remove old code, add german lang

This commit is contained in:
Eduard Gert
2026-05-15 12:56:09 +02:00
parent 17cae1a75c
commit 5411fa4350
25 changed files with 175 additions and 827 deletions
+16 -11
View File
@@ -1,23 +1,28 @@
import { useState } from "react";
import { Outlet } from "react-router-dom";
import { Header } from "@/layouts/Header.tsx";
import { AppearanceProvider } from "@/modules/appearance/AppearanceContext.tsx";
import { ClientVersionProvider } from "@/modules/auto-update/ClientVersionContext.tsx";
import { DebugBundleProvider } from "@/modules/debug-bundle/DebugBundleContext.tsx";
import { ProfileProvider } from "@/modules/profile/ProfileContext.tsx";
// The wide-panel toggle lives in plain React state here so every launch
// starts in the small layout — no localStorage, no cross-machine drift.
// Header drives the toggle; Main reads it via Outlet context to decide
// whether to mount the right-side panel.
export type MainOutletContext = { expanded: boolean };
export const AppLayout = () => {
const [expanded, setExpanded] = useState(false);
return (
<div className={"relative flex h-full flex-col"}>
<AppearanceProvider>
<ProfileProvider>
<DebugBundleProvider>
<ClientVersionProvider>
<Header />
<Outlet />
</ClientVersionProvider>
</DebugBundleProvider>
</ProfileProvider>
</AppearanceProvider>
<ProfileProvider>
<DebugBundleProvider>
<ClientVersionProvider>
<Header expanded={expanded} setExpanded={setExpanded} />
<Outlet context={{ expanded } satisfies MainOutletContext} />
</ClientVersionProvider>
</DebugBundleProvider>
</ProfileProvider>
</div>
);
};
@@ -1,117 +0,0 @@
import { useEffect, useRef, useState } from "react";
import { ConnectionState, NetBirdConnectToggle } from "@/components/NetBirdConnectToggle.tsx";
import Button from "@/components/Button.tsx";
import { cn } from "@/lib/cn.ts";
const CONNECT_DURATION_MS = 1500;
const DISCONNECT_DURATION_MS = 800;
const STATUS_LABEL: Record<ConnectionState, string> = {
[ConnectionState.Disconnected]: "Disconnected",
[ConnectionState.Connecting]: "Connecting...",
[ConnectionState.Connected]: "Connected",
[ConnectionState.Disconnecting]: "Disconnecting...",
};
export const ConnectionStatus = () => {
const [state, setState] = useState<ConnectionState>(ConnectionState.Disconnected);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(
() => () => {
if (timerRef.current) clearTimeout(timerRef.current);
},
[],
);
const transition = (next: ConnectionState, after: ConnectionState, delay: number) => {
if (timerRef.current) clearTimeout(timerRef.current);
setState(next);
timerRef.current = setTimeout(() => {
setState(after);
timerRef.current = null;
}, delay);
};
const connect = () =>
transition(ConnectionState.Connecting, ConnectionState.Connected, CONNECT_DURATION_MS);
const disconnect = () =>
transition(
ConnectionState.Disconnecting,
ConnectionState.Disconnected,
DISCONNECT_DURATION_MS,
);
const handleToggleClick = () => {
if (state === ConnectionState.Disconnected) connect();
else if (state === ConnectionState.Connected) disconnect();
};
const handleButtonClick = () => {
if (state === ConnectionState.Disconnected) {
connect();
return;
}
if (state === ConnectionState.Connected) {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
setState(ConnectionState.Disconnected);
}
};
const isTransitioning =
state === ConnectionState.Connecting || state === ConnectionState.Disconnecting;
const isConnectedSide =
state === ConnectionState.Connected || state === ConnectionState.Disconnecting;
const buttonLabel = isConnectedSide ? "Disconnect" : "Connect";
const buttonVariant = isConnectedSide ? "secondary" : "primary";
return (
<div className={cn("flex flex-col h-full w-full items-center justify-between", "-mt-4")}>
<div className={"w-full h-full flex flex-col items-center justify-center"}>
<div className={"flex flex-col items-center justify-center"}>
<p
className={
"font-mono text-xs text-nb-gray-300 transition-opacity duration-300 " +
(state === ConnectionState.Connected ? "opacity-100" : "opacity-0")
}
>
peer-hostname.netbird.cloud
</p>
<p
className={
"font-mono text-xs text-nb-gray-300 mt-0.5 mb-6 transition-opacity duration-300 " +
(state === ConnectionState.Connected ? "opacity-100" : "opacity-0")
}
>
192.168.0.1
</p>
</div>
<NetBirdConnectToggle state={state} onClick={handleToggleClick} />
<div
className={
"flex flex-col w-full items-center justify-center gap-3 p-4 rounded-2xl mt-2"
}
>
<h1 className={"text-sm font-medium text-nb-gray-200 tracking-wide"}>
{STATUS_LABEL[state]}
</h1>
<div className={"w-full"}>
<Button
variant={buttonVariant}
size={"xs"}
className={"w-full"}
disabled={isTransitioning}
onClick={handleButtonClick}
>
{buttonLabel}
</Button>
</div>
</div>
</div>
</div>
);
};
@@ -1,13 +1,19 @@
import { useMemo, useState } from "react";
import { Dialogs, Events } from "@wailsio/runtime";
import { Connection, WindowManager } from "@bindings/services";
import { ConnectionState } from "@/components/NetBirdConnectToggle.tsx";
import { ToggleSwitch } from "@/components/ToggleSwitch.tsx";
import { useStatus } from "@/hooks/useStatus";
import { useProfile } from "@/modules/profile/ProfileContext.tsx";
import { cn } from "@/lib/cn.ts";
import netbirdFullLogo from "@/assets/logos/netbird-full.svg";
enum ConnectionState {
Disconnected = "disconnected",
Connecting = "connecting",
Connected = "connected",
Disconnecting = "disconnecting",
}
const STATUS_LABEL: Record<ConnectionState, string> = {
[ConnectionState.Disconnected]: "Disconnected",
[ConnectionState.Connecting]: "Connecting...",
+13 -14
View File
@@ -4,7 +4,6 @@ import { Window } from "@wailsio/runtime";
import { WindowManager } from "@bindings/services";
import { ProfileSelector } from "@/components/ProfileSelector.tsx";
import { IconButton } from "@/components/IconButton.tsx";
import { useAppearance } from "@/modules/appearance/AppearanceContext.tsx";
import { cn } from "@/lib/cn";
const WINDOW_SMALL_WIDTH = 380;
@@ -12,8 +11,12 @@ const WINDOW_BIG_WIDTH = 925;
const WINDOW_HEIGHT = 615;
const EXPANDED_THRESHOLD = 500;
export const Header = () => {
const { showProfileSelector, showSettingsButton, expanded, setField } = useAppearance();
type HeaderProps = {
expanded: boolean;
setExpanded: (next: boolean) => void;
};
export const Header = ({ expanded, setExpanded }: HeaderProps) => {
const didInitialResize = useRef(false);
useEffect(() => {
@@ -33,15 +36,15 @@ export const Header = () => {
useEffect(() => {
const onResize = () => {
const isWide = window.innerWidth >= EXPANDED_THRESHOLD;
if (isWide !== expanded) setField("expanded", isWide);
if (isWide !== expanded) setExpanded(isWide);
};
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, [expanded, setField]);
}, [expanded, setExpanded]);
const togglePanel = () => {
const next = !expanded;
setField("expanded", next);
setExpanded(next);
const w = next ? WINDOW_BIG_WIDTH : WINDOW_SMALL_WIDTH;
void Window.SetSize(w, WINDOW_HEIGHT).catch(() => {});
};
@@ -57,19 +60,15 @@ export const Header = () => {
"pt-4",
)}
>
{showProfileSelector && (
<div className={"ml-20"}>
<ProfileSelector />
</div>
)}
<div className={"ml-20"}>
<ProfileSelector />
</div>
<IconButton
icon={expanded ? PanelRightOpenIcon : PanelRightCloseIcon}
onClick={togglePanel}
/>
{showSettingsButton && (
<IconButton icon={SettingsIcon} onClick={openSettings} />
)}
<IconButton icon={SettingsIcon} onClick={openSettings} />
</div>
);
};
+4 -8
View File
@@ -1,13 +1,13 @@
import { ConnectionStatus } from "@/layouts/ConnectionStatus.tsx";
import { useOutletContext } from "react-router-dom";
import { ConnectionStatusSwitch } from "@/layouts/ConnectionStatusSwitch.tsx";
import { MainRightSide } from "@/layouts/MainRightSide.tsx";
import { Navigation } from "@/layouts/Navigation.tsx";
import { Peers } from "@/modules/peers/Peers.tsx";
import { useAppearance } from "@/modules/appearance/AppearanceContext.tsx";
import type { MainOutletContext } from "@/layouts/AppLayout.tsx";
import { cn } from "@/lib/cn";
export const Main = () => {
const { connectionLayout, expanded } = useAppearance();
const { expanded } = useOutletContext<MainOutletContext>();
return (
<div className={"wails-draggable flex flex-1 min-h-0 p-4 gap-4"}>
<div
@@ -16,11 +16,7 @@ export const Main = () => {
expanded && "max-w-xs",
)}
>
{connectionLayout === "switch" ? (
<ConnectionStatusSwitch />
) : (
<ConnectionStatus />
)}
<ConnectionStatusSwitch />
<Navigation peersActive />
</div>
{expanded && (
+26 -35
View File
@@ -1,7 +1,6 @@
import { CardNavItem } from "@/components/CardNavItem.tsx";
import { Layers3Icon, MonitorSmartphoneIcon } from "lucide-react";
import deFlag from "@/assets/flags/1x1/de.svg";
import { useAppearance } from "@/modules/appearance/AppearanceContext.tsx";
type Props = {
peersActive?: boolean;
@@ -9,42 +8,34 @@ type Props = {
};
export const Navigation = ({ peersActive = false, onPeersClick }: Props) => {
const { showPeersNav, showResourcesNav, showExitNodeNav } = useAppearance();
return (
<nav className={"w-full flex flex-col gap-1 mt-auto"}>
{showPeersNav && (
<CardNavItem
icon={MonitorSmartphoneIcon}
title={"Peers"}
description={"17 of 25 Online"}
active={peersActive}
onClick={onPeersClick}
/>
)}
{showResourcesNav && (
<CardNavItem
icon={Layers3Icon}
title={"Resources"}
description={"13 of 16 Active"}
iconSize={14}
/>
)}
{showExitNodeNav && (
<CardNavItem
iconNode={
<img
src={deFlag}
alt={"Germany"}
className={
"h-6 w-6 rounded-full border-[3px] border-nb-gray-850 shrink-0"
}
/>
}
title={"Exit Node Berlin"}
description={"100.92.14.37"}
/>
)}
<CardNavItem
icon={MonitorSmartphoneIcon}
title={"Peers"}
description={"17 of 25 Online"}
active={peersActive}
onClick={onPeersClick}
/>
<CardNavItem
icon={Layers3Icon}
title={"Resources"}
description={"13 of 16 Active"}
iconSize={14}
/>
<CardNavItem
iconNode={
<img
src={deFlag}
alt={"Germany"}
className={
"h-6 w-6 rounded-full border-[3px] border-nb-gray-850 shrink-0"
}
/>
}
title={"Exit Node Berlin"}
description={"100.92.14.37"}
/>
</nav>
);
};
@@ -1,5 +1,4 @@
import { Outlet } from "react-router-dom";
import { AppearanceProvider } from "@/modules/appearance/AppearanceContext.tsx";
import { ClientVersionProvider } from "@/modules/auto-update/ClientVersionContext.tsx";
import { DebugBundleProvider } from "@/modules/debug-bundle/DebugBundleContext.tsx";
import { ProfileProvider } from "@/modules/profile/ProfileContext.tsx";
@@ -10,27 +9,26 @@ import { ProfileProvider } from "@/modules/profile/ProfileContext.tsx";
// selector / panel toggle / settings icon.
//
// The 38px placeholder strip at the top accounts for the macOS
// `MacTitleBarHiddenInset` setting in services/windows.go: the native title
// bar is invisible but the traffic-light buttons still float in the top-left
// corner. Without this strip the buttons would overlap the settings content.
// The strip is `wails-draggable` so users can move the window by dragging it.
// `MacTitleBarHiddenInset` setting in services/windowmanager.go: the native
// title bar is invisible but the traffic-light buttons still float in the
// top-left corner. Without this strip the buttons would overlap the settings
// content. The strip is `wails-draggable` so users can move the window by
// dragging it.
export const SettingsLayout = () => {
return (
<div className={"relative flex h-full flex-col"}>
<AppearanceProvider>
<ProfileProvider>
<DebugBundleProvider>
<ClientVersionProvider>
<div
className={
"wails-draggable cursor-default select-none h-[38px] shrink-0"
}
/>
<Outlet />
</ClientVersionProvider>
</DebugBundleProvider>
</ProfileProvider>
</AppearanceProvider>
<ProfileProvider>
<DebugBundleProvider>
<ClientVersionProvider>
<div
className={
"wails-draggable cursor-default select-none h-[38px] shrink-0"
}
/>
<Outlet />
</ClientVersionProvider>
</DebugBundleProvider>
</ProfileProvider>
</div>
);
};