diff --git a/client/ui/frontend/src/modules/profiles/ProfileDropdown.tsx b/client/ui/frontend/src/modules/profiles/ProfileDropdown.tsx
index 4c1134cc2..92082583f 100644
--- a/client/ui/frontend/src/modules/profiles/ProfileDropdown.tsx
+++ b/client/ui/frontend/src/modules/profiles/ProfileDropdown.tsx
@@ -4,10 +4,9 @@ import { Dialogs } from "@wailsio/runtime";
import * as Popover from "@radix-ui/react-popover";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { Command } from "cmdk";
-import { Check, ChevronDown, PlusCircle, Settings2, UserCircle } from "lucide-react";
+import { Check, ChevronDown, Settings2, UserCircle } from "lucide-react";
import { pickProfileIcon } from "@/modules/profiles/ProfileAvatar";
import type { Profile } from "@bindings/services/models.js";
-import { ProfileCreationModal } from "@/modules/profiles/ProfileCreationModal";
import { Tooltip } from "@/components/Tooltip";
import { useProfile } from "@/contexts/ProfileContext";
import { cn } from "@/lib/cn";
@@ -17,14 +16,12 @@ type ProfileDropdownProps = {
onManageProfiles?: () => void;
};
-const ADD_VALUE = "__add_profile__";
const MANAGE_VALUE = "__manage_profiles__";
export const ProfileDropdown = ({ onManageProfiles }: ProfileDropdownProps) => {
const { t } = useTranslation();
- const { activeProfile, profiles, addProfile, switchProfile } = useProfile();
+ const { activeProfile, profiles, switchProfile } = useProfile();
const [open, setOpen] = useState(false);
- const [newProfileOpen, setNewProfileOpen] = useState(false);
const [busy, setBusy] = useState(false);
const sortedProfiles = [...profiles].sort((a, b) => {
@@ -54,28 +51,11 @@ export const ProfileDropdown = ({ onManageProfiles }: ProfileDropdownProps) => {
void guarded(t("profile.error.switchTitle"), () => switchProfile(name));
};
- const handleAdd = () => {
- setOpen(false);
- setNewProfileOpen(true);
- };
-
const handleManage = () => {
setOpen(false);
onManageProfiles?.();
};
- const handleCreateProfile = async (name: string) => {
- try {
- await addProfile(name);
- await switchProfile(name);
- } catch (e) {
- await Dialogs.Error({
- Title: t("profile.error.createTitle"),
- Message: formatErrorMessage(e),
- });
- }
- };
-
const displayName = activeProfile || t("profile.selector.loading");
return (
@@ -130,20 +110,6 @@ export const ProfileDropdown = ({ onManageProfiles }: ProfileDropdownProps) => {
)}
-
-
-
- {t("profile.dropdown.addProfile")}
-
-
{
-
>
);
};
diff --git a/client/ui/frontend/src/modules/settings/SettingsPage.tsx b/client/ui/frontend/src/modules/settings/SettingsPage.tsx
index 032450295..34026c59d 100644
--- a/client/ui/frontend/src/modules/settings/SettingsPage.tsx
+++ b/client/ui/frontend/src/modules/settings/SettingsPage.tsx
@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
-import { useLocation, useSearchParams } from "react-router-dom";
+import { useLocation } from "react-router-dom";
+import { Events } from "@wailsio/runtime";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { cn } from "@/lib/cn";
import { AppRightPanel } from "@/layouts/AppRightPanel.tsx";
@@ -15,11 +16,18 @@ import { SettingsAdvanced } from "@/modules/settings/SettingsAdvanced.tsx";
import { SettingsTroubleshooting } from "@/modules/settings/SettingsTroubleshooting.tsx";
import { SettingsAbout } from "@/modules/settings/SettingsAbout.tsx";
-// The settings window opens at General by default. Navigation state (e.g. the
-// update-available header trigger jumps to About) or a `?tab=` query param
-// in the window's start URL (e.g. WindowManager.OpenSettings("profiles") from
-// the profile dropdown) override the default. No persistence across opens —
-// a user who wants to revisit a deep tab gets there in two clicks.
+const EVENT_SETTINGS_OPEN = "netbird:settings:open";
+
+// The settings window mounts once at app startup (hidden) and stays at the
+// single URL `/#/settings` forever — no SetURL between opens, so the
+// `AppLayout` provider stack never re-mounts and we never see the
+// `SettingsSkeleton` flash mid-reload. Tab is local state, driven by:
+// - the `netbird:settings:open` Wails event from `WindowManager.OpenSettings`
+// (sets the target tab, then Go calls `Show`/`Focus`); and
+// - the same event with payload `"general"` from the close hook, so the
+// window is already on General the next time Show fires (common case).
+// In-window navigation state (e.g. the update-available header jump to About)
+// still wins for that one render.
//
// The `h-12` draggable strip at the top accounts for the macOS
// `MacTitleBarHiddenInset` setting in services/windowmanager.go (traffic-light
@@ -27,17 +35,19 @@ import { SettingsAbout } from "@/modules/settings/SettingsAbout.tsx";
// Header height so AppRightPanel ends up the same height in both windows.
export const SettingsPage = () => {
const location = useLocation();
- const [searchParams] = useSearchParams();
- const queryTab = searchParams.get("tab");
const navState = location.state as { tab?: string } | null;
- const [active, setActive] = useState(
- () => navState?.tab ?? queryTab ?? "general",
- );
+ const [active, setActive] = useState(() => navState?.tab ?? "general");
useEffect(() => {
if (navState?.tab) setActive(navState.tab);
}, [navState?.tab, location.key]);
+ useEffect(() => {
+ return Events.On(EVENT_SETTINGS_OPEN, (e: { data: string }) => {
+ setActive(e.data || "general");
+ });
+ }, []);
+
return (
<>