mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-21 04:49:10 +02:00
update ui for win11
This commit is contained in:
@@ -11,7 +11,7 @@ const Root = forwardRef<
|
|||||||
<Tabs.Root
|
<Tabs.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
orientation={"vertical"}
|
orientation={"vertical"}
|
||||||
className={cn("flex flex-1 min-h-0 gap-4", className)}
|
className={cn("flex flex-1 min-h-0", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -22,7 +22,7 @@ const List = forwardRef<HTMLDivElement, Tabs.TabsListProps>(
|
|||||||
return (
|
return (
|
||||||
<Tabs.List
|
<Tabs.List
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("w-full flex flex-col gap-1", className)}
|
className={cn("w-full flex flex-col gap-1 p-4 pr-0", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const AppRightPanel = ({ children, overlay, overlayOpen = false }: Props)
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"wails-no-draggable relative",
|
"wails-no-draggable relative m-4",
|
||||||
"bg-nb-gray-940 border border-nb-gray-920",
|
"bg-nb-gray-940 border border-nb-gray-920",
|
||||||
"flex-1 min-h-0 min-w-0 flex flex-col rounded-xl rounded-br-2xl overflow-hidden",
|
"flex-1 min-h-0 min-w-0 flex flex-col rounded-xl rounded-br-2xl overflow-hidden",
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -143,10 +143,10 @@ export const MainHeader = () => {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"shrink-0 cursor-default wails-draggable relative",
|
"shrink-0 cursor-default wails-draggable relative",
|
||||||
"flex items-center h-12 px-3 top-2.5",
|
"flex items-center h-12 top-2.5",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={"grid grid-cols-3 items-center w-[356px] shrink-0"}>
|
<div className={"grid grid-cols-3 items-center w-[364px] shrink-0"}>
|
||||||
<div />
|
<div />
|
||||||
<div className={"flex justify-center ml-4"}>{profileSlot}</div>
|
<div className={"flex justify-center ml-4"}>{profileSlot}</div>
|
||||||
<div />
|
<div />
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ const MainBody = () => {
|
|||||||
const isAdvanced = viewMode === "advanced";
|
const isAdvanced = viewMode === "advanced";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"wails-draggable flex flex-1 min-h-0 p-4 gap-4"}>
|
<div className={"wails-draggable flex flex-1 min-h-0 gap-4r"}>
|
||||||
<div
|
<div
|
||||||
className={"flex flex-col items-center shrink-0 w-[348px]"}
|
className={"flex flex-col items-center shrink-0 w-[364px]"}
|
||||||
>
|
>
|
||||||
<MainConnectionStatusSwitch />
|
<MainConnectionStatusSwitch />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useLocation } from "react-router-dom";
|
|||||||
import { Events } from "@wailsio/runtime";
|
import { Events } from "@wailsio/runtime";
|
||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { cn } from "@/lib/cn";
|
import { cn } from "@/lib/cn";
|
||||||
|
import { isMacOS } from "@/lib/platform";
|
||||||
import { AppRightPanel } from "@/layouts/AppRightPanel.tsx";
|
import { AppRightPanel } from "@/layouts/AppRightPanel.tsx";
|
||||||
import { VerticalTabs } from "@/components/VerticalTabs.tsx";
|
import { VerticalTabs } from "@/components/VerticalTabs.tsx";
|
||||||
import { SettingsNavigation } from "@/modules/settings/SettingsNavigation.tsx";
|
import { SettingsNavigation } from "@/modules/settings/SettingsNavigation.tsx";
|
||||||
@@ -50,15 +51,18 @@ export const SettingsPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
{isMacOS() ? (
|
||||||
className={
|
<div
|
||||||
"wails-draggable cursor-default select-none h-12 shrink-0"
|
className={
|
||||||
}
|
"wails-draggable cursor-default select-none h-12 shrink-0"
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className={"h-px shrink-0 bg-nb-gray-920/0"} />
|
||||||
|
)}
|
||||||
<VerticalTabs
|
<VerticalTabs
|
||||||
value={active}
|
value={active}
|
||||||
onValueChange={setActive}
|
onValueChange={setActive}
|
||||||
className={"p-4"}
|
|
||||||
>
|
>
|
||||||
<SettingsNavigation />
|
<SettingsNavigation />
|
||||||
<AppRightPanel>
|
<AppRightPanel>
|
||||||
|
|||||||
+4
-10
@@ -192,19 +192,13 @@ func main() {
|
|||||||
Width: initialWidth,
|
Width: initialWidth,
|
||||||
Height: 640,
|
Height: 640,
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
BackgroundColour: application.NewRGB(24, 26, 29),
|
BackgroundColour: services.WindowBackgroundColour,
|
||||||
URL: "/",
|
URL: "/",
|
||||||
DisableResize: true,
|
DisableResize: true,
|
||||||
|
MinimiseButtonState: application.ButtonHidden,
|
||||||
MaximiseButtonState: application.ButtonHidden,
|
MaximiseButtonState: application.ButtonHidden,
|
||||||
Mac: application.MacWindow{
|
Mac: services.AppleMacOSAppearanceOptions(),
|
||||||
InvisibleTitleBarHeight: 38,
|
Windows: services.MicrosoftWindowsAppearanceOptions(),
|
||||||
Backdrop: application.MacBackdropNormal,
|
|
||||||
TitleBar: application.MacTitleBarHiddenInset,
|
|
||||||
CollectionBehavior: application.MacWindowCollectionBehaviorFullScreenNone,
|
|
||||||
},
|
|
||||||
Windows: application.WindowsWindow{
|
|
||||||
Theme: application.Dark,
|
|
||||||
},
|
|
||||||
Linux: application.LinuxWindow{
|
Linux: application.LinuxWindow{
|
||||||
Icon: iconWindow,
|
Icon: iconWindow,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,6 +28,81 @@ const EventBrowserLoginCancel = "browser-login:cancel"
|
|||||||
// flashed the SettingsSkeleton between opens.
|
// flashed the SettingsSkeleton between opens.
|
||||||
const EventSettingsOpen = "netbird:settings:open"
|
const EventSettingsOpen = "netbird:settings:open"
|
||||||
|
|
||||||
|
// WindowBackgroundColour is the shared in-window background for every
|
||||||
|
// NetBird webview (matches the bg-nb-gray utility in the Tailwind config
|
||||||
|
// at #181A1D / nb-gray-950, used by AppLayout's <html> background).
|
||||||
|
var WindowBackgroundColour = application.NewRGB(24, 26, 29)
|
||||||
|
|
||||||
|
// Wails reads CustomTheme colours as 0x00BBGGRR (RGB byte order reversed).
|
||||||
|
// Border + title bar match AppRightPanel's bg-nb-gray-940 (#1C1E21);
|
||||||
|
// title text matches text-nb-gray-100 (#E4E7E9). u32ptr exists only
|
||||||
|
// because WindowTheme fields are *uint32 and Go has no literal address-of.
|
||||||
|
func u32ptr(v uint32) *uint32 { return &v }
|
||||||
|
|
||||||
|
var microsoftWindowsTheme = &application.WindowTheme{
|
||||||
|
BorderColour: u32ptr(0x00211E1C),
|
||||||
|
TitleBarColour: u32ptr(0x00211E1C),
|
||||||
|
TitleTextColour: u32ptr(0x00E9E7E4),
|
||||||
|
}
|
||||||
|
|
||||||
|
// MicrosoftWindowsAppearanceOptions is the per-window Microsoft Windows OS
|
||||||
|
// chrome shared by every NetBird webview window. Mica backdrop (no-op on
|
||||||
|
// pre-22621), dark theme, custom title bar/border colours so the chrome
|
||||||
|
// reads as an extension of the in-window AppRightPanel.
|
||||||
|
func MicrosoftWindowsAppearanceOptions() application.WindowsWindow {
|
||||||
|
return application.WindowsWindow{
|
||||||
|
BackdropType: application.Mica,
|
||||||
|
Theme: application.Dark,
|
||||||
|
CustomTheme: application.ThemeSettings{
|
||||||
|
DarkModeActive: microsoftWindowsTheme,
|
||||||
|
DarkModeInactive: microsoftWindowsTheme,
|
||||||
|
LightModeActive: microsoftWindowsTheme,
|
||||||
|
LightModeInactive: microsoftWindowsTheme,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppleMacOSAppearanceOptions is the per-window macOS chrome shared by
|
||||||
|
// every NetBird webview window. The hidden title bar inset clears space
|
||||||
|
// for the traffic-light buttons; the FullScreenNone collection behavior
|
||||||
|
// keeps the green button from offering a full-screen mode that breaks
|
||||||
|
// our fixed-size layouts.
|
||||||
|
func AppleMacOSAppearanceOptions() application.MacWindow {
|
||||||
|
return application.MacWindow{
|
||||||
|
InvisibleTitleBarHeight: 38,
|
||||||
|
Backdrop: application.MacBackdropNormal,
|
||||||
|
TitleBar: application.MacTitleBarHiddenInset,
|
||||||
|
CollectionBehavior: application.MacWindowCollectionBehaviorFullScreenNone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialogWindowOptions is the baseline for every auxiliary dialog window
|
||||||
|
// (BrowserLogin, SessionExpired, SessionAboutToExpire, InstallProgress).
|
||||||
|
// All four share size (360x320), the no-resize / no-min / no-max chrome,
|
||||||
|
// Hidden-on-create (so the React side can auto-size before first paint),
|
||||||
|
// AlwaysOnTop (the dialogs interrupt the user, the SSO popup overrides
|
||||||
|
// this), and the shared background/Mac/Windows appearance. Callers fill
|
||||||
|
// in per-dialog overrides (URL params, screen targeting, etc.) on the
|
||||||
|
// returned value before passing it to Window.NewWithOptions.
|
||||||
|
func DialogWindowOptions(name, title, url string) application.WebviewWindowOptions {
|
||||||
|
return application.WebviewWindowOptions{
|
||||||
|
Name: name,
|
||||||
|
Title: title,
|
||||||
|
Width: 360,
|
||||||
|
Height: 320,
|
||||||
|
DisableResize: true,
|
||||||
|
AlwaysOnTop: true,
|
||||||
|
Hidden: true,
|
||||||
|
MinimiseButtonState: application.ButtonHidden,
|
||||||
|
MaximiseButtonState: application.ButtonHidden,
|
||||||
|
CloseButtonState: application.ButtonEnabled,
|
||||||
|
BackgroundColour: WindowBackgroundColour,
|
||||||
|
URL: url,
|
||||||
|
Mac: AppleMacOSAppearanceOptions(),
|
||||||
|
Windows: MicrosoftWindowsAppearanceOptions(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WindowManager opens auxiliary application windows on demand from the
|
// WindowManager opens auxiliary application windows on demand from the
|
||||||
// frontend. The main window is created up-front in main.go; this service is
|
// frontend. The main window is created up-front in main.go; this service is
|
||||||
// for secondary surfaces (Settings, BrowserLogin, Session*, InstallProgress).
|
// for secondary surfaces (Settings, BrowserLogin, Session*, InstallProgress).
|
||||||
@@ -68,7 +143,7 @@ func NewWindowManager(app *application.App, mainWindow *application.WebviewWindo
|
|||||||
s := &WindowManager{app: app, mainWindow: mainWindow}
|
s := &WindowManager{app: app, mainWindow: mainWindow}
|
||||||
s.settings = app.Window.NewWithOptions(application.WebviewWindowOptions{
|
s.settings = app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||||
Name: "settings",
|
Name: "settings",
|
||||||
Title: "NetBird Settings",
|
Title: "Settings",
|
||||||
Width: 900,
|
Width: 900,
|
||||||
Height: 640,
|
Height: 640,
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
@@ -76,17 +151,10 @@ func NewWindowManager(app *application.App, mainWindow *application.WebviewWindo
|
|||||||
MinimiseButtonState: application.ButtonHidden,
|
MinimiseButtonState: application.ButtonHidden,
|
||||||
MaximiseButtonState: application.ButtonHidden,
|
MaximiseButtonState: application.ButtonHidden,
|
||||||
CloseButtonState: application.ButtonEnabled,
|
CloseButtonState: application.ButtonEnabled,
|
||||||
BackgroundColour: application.NewRGB(24, 26, 29),
|
BackgroundColour: WindowBackgroundColour,
|
||||||
URL: "/#/settings",
|
URL: "/#/settings",
|
||||||
Mac: application.MacWindow{
|
Mac: AppleMacOSAppearanceOptions(),
|
||||||
InvisibleTitleBarHeight: 38,
|
Windows: MicrosoftWindowsAppearanceOptions(),
|
||||||
Backdrop: application.MacBackdropNormal,
|
|
||||||
TitleBar: application.MacTitleBarHiddenInset,
|
|
||||||
CollectionBehavior: application.MacWindowCollectionBehaviorFullScreenNone,
|
|
||||||
},
|
|
||||||
Windows: application.WindowsWindow{
|
|
||||||
Theme: application.Dark,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
// Hide on close instead of destroying — preserves in-window React state
|
// Hide on close instead of destroying — preserves in-window React state
|
||||||
// across reopens. Mirrors the main window's close behaviour. Resetting
|
// across reopens. Mirrors the main window's close behaviour. Resetting
|
||||||
@@ -143,36 +211,17 @@ func (s *WindowManager) OpenBrowserLogin(uri string) {
|
|||||||
screen = sc
|
screen = sc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.browserLogin = s.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
opts := DialogWindowOptions("browser-login", "NetBird Sign-in", startURL)
|
||||||
Name: "browser-login",
|
// SSO popup deliberately is NOT always-on-top — the user moves
|
||||||
Title: "NetBird Sign-in",
|
// between the browser tab and our popup; pinning it would obscure
|
||||||
Width: 360,
|
// the browser at the moment they need to interact with it.
|
||||||
Height: 320,
|
opts.AlwaysOnTop = false
|
||||||
DisableResize: true,
|
// WindowCentered + Screen centers on the chosen display's
|
||||||
// Hidden so the React side can measure its content via
|
// WorkArea (see WebviewWindowOptions.Screen docs) so the popup
|
||||||
// useAutoSizeWindow and call Window.SetSize + Show before the
|
// follows the user onto the screen they're already looking at.
|
||||||
// user sees the placeholder snapping to the measured height,
|
opts.InitialPosition = application.WindowCentered
|
||||||
// matching the Session* windows.
|
opts.Screen = screen
|
||||||
Hidden: true,
|
s.browserLogin = s.app.Window.NewWithOptions(opts)
|
||||||
// WindowCentered + Screen centers on the chosen display's
|
|
||||||
// WorkArea (see WebviewWindowOptions.Screen docs).
|
|
||||||
InitialPosition: application.WindowCentered,
|
|
||||||
Screen: screen,
|
|
||||||
MinimiseButtonState: application.ButtonHidden,
|
|
||||||
MaximiseButtonState: application.ButtonHidden,
|
|
||||||
CloseButtonState: application.ButtonEnabled,
|
|
||||||
BackgroundColour: application.NewRGB(24, 26, 29),
|
|
||||||
URL: startURL,
|
|
||||||
Mac: application.MacWindow{
|
|
||||||
InvisibleTitleBarHeight: 38,
|
|
||||||
Backdrop: application.MacBackdropTranslucent,
|
|
||||||
TitleBar: application.MacTitleBarHiddenInset,
|
|
||||||
CollectionBehavior: application.MacWindowCollectionBehaviorFullScreenNone,
|
|
||||||
},
|
|
||||||
Windows: application.WindowsWindow{
|
|
||||||
Theme: application.Dark,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
bl := s.browserLogin
|
bl := s.browserLogin
|
||||||
// User-initiated close (red X) means cancel. Emit the event so
|
// User-initiated close (red X) means cancel. Emit the event so
|
||||||
// startLogin() can tear the SSO wait down, then let the window
|
// startLogin() can tear the SSO wait down, then let the window
|
||||||
@@ -271,29 +320,9 @@ func (s *WindowManager) OpenSessionExpired() {
|
|||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
if s.sessionExpired == nil {
|
if s.sessionExpired == nil {
|
||||||
s.sessionExpired = s.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
s.sessionExpired = s.app.Window.NewWithOptions(
|
||||||
Name: "session-expired",
|
DialogWindowOptions("session-expired", "NetBird", "/#/dialog/session-expired"),
|
||||||
Title: "NetBird",
|
)
|
||||||
Width: 360,
|
|
||||||
Height: 320,
|
|
||||||
DisableResize: true,
|
|
||||||
AlwaysOnTop: true,
|
|
||||||
Hidden: true,
|
|
||||||
MinimiseButtonState: application.ButtonHidden,
|
|
||||||
MaximiseButtonState: application.ButtonHidden,
|
|
||||||
CloseButtonState: application.ButtonEnabled,
|
|
||||||
BackgroundColour: application.NewRGB(24, 26, 29),
|
|
||||||
URL: "/#/dialog/session-expired",
|
|
||||||
Mac: application.MacWindow{
|
|
||||||
InvisibleTitleBarHeight: 38,
|
|
||||||
Backdrop: application.MacBackdropTranslucent,
|
|
||||||
TitleBar: application.MacTitleBarHiddenInset,
|
|
||||||
CollectionBehavior: application.MacWindowCollectionBehaviorFullScreenNone,
|
|
||||||
},
|
|
||||||
Windows: application.WindowsWindow{
|
|
||||||
Theme: application.Dark,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
s.sessionExpired.OnWindowEvent(events.Common.WindowClosing, func(_ *application.WindowEvent) {
|
s.sessionExpired.OnWindowEvent(events.Common.WindowClosing, func(_ *application.WindowEvent) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.sessionExpired = nil
|
s.sessionExpired = nil
|
||||||
@@ -326,29 +355,9 @@ func (s *WindowManager) OpenSessionAboutToExpire(seconds int) {
|
|||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
startURL := "/#/dialog/session-about-to-expire?seconds=" + strconv.Itoa(seconds)
|
startURL := "/#/dialog/session-about-to-expire?seconds=" + strconv.Itoa(seconds)
|
||||||
if s.sessionAboutToExpire == nil {
|
if s.sessionAboutToExpire == nil {
|
||||||
s.sessionAboutToExpire = s.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
s.sessionAboutToExpire = s.app.Window.NewWithOptions(
|
||||||
Name: "session-about-to-expire",
|
DialogWindowOptions("session-about-to-expire", "NetBird", startURL),
|
||||||
Title: "NetBird",
|
)
|
||||||
Width: 360,
|
|
||||||
Height: 320,
|
|
||||||
DisableResize: true,
|
|
||||||
AlwaysOnTop: true,
|
|
||||||
Hidden: true,
|
|
||||||
MinimiseButtonState: application.ButtonHidden,
|
|
||||||
MaximiseButtonState: application.ButtonHidden,
|
|
||||||
CloseButtonState: application.ButtonEnabled,
|
|
||||||
BackgroundColour: application.NewRGB(24, 26, 29),
|
|
||||||
URL: startURL,
|
|
||||||
Mac: application.MacWindow{
|
|
||||||
InvisibleTitleBarHeight: 38,
|
|
||||||
Backdrop: application.MacBackdropTranslucent,
|
|
||||||
TitleBar: application.MacTitleBarHiddenInset,
|
|
||||||
CollectionBehavior: application.MacWindowCollectionBehaviorFullScreenNone,
|
|
||||||
},
|
|
||||||
Windows: application.WindowsWindow{
|
|
||||||
Theme: application.Dark,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
s.sessionAboutToExpire.OnWindowEvent(events.Common.WindowClosing, func(_ *application.WindowEvent) {
|
s.sessionAboutToExpire.OnWindowEvent(events.Common.WindowClosing, func(_ *application.WindowEvent) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.sessionAboutToExpire = nil
|
s.sessionAboutToExpire = nil
|
||||||
@@ -392,29 +401,9 @@ func (s *WindowManager) OpenInstallProgress(version string) {
|
|||||||
}
|
}
|
||||||
if s.installProgress == nil {
|
if s.installProgress == nil {
|
||||||
s.hideOtherWindowsLocked("install-progress")
|
s.hideOtherWindowsLocked("install-progress")
|
||||||
s.installProgress = s.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
s.installProgress = s.app.Window.NewWithOptions(
|
||||||
Name: "install-progress",
|
DialogWindowOptions("install-progress", "NetBird", startURL),
|
||||||
Title: "NetBird",
|
)
|
||||||
Width: 360,
|
|
||||||
Height: 320,
|
|
||||||
DisableResize: true,
|
|
||||||
AlwaysOnTop: true,
|
|
||||||
Hidden: true,
|
|
||||||
MinimiseButtonState: application.ButtonHidden,
|
|
||||||
MaximiseButtonState: application.ButtonHidden,
|
|
||||||
CloseButtonState: application.ButtonEnabled,
|
|
||||||
BackgroundColour: application.NewRGB(24, 26, 29),
|
|
||||||
URL: startURL,
|
|
||||||
Mac: application.MacWindow{
|
|
||||||
InvisibleTitleBarHeight: 38,
|
|
||||||
Backdrop: application.MacBackdropTranslucent,
|
|
||||||
TitleBar: application.MacTitleBarHiddenInset,
|
|
||||||
CollectionBehavior: application.MacWindowCollectionBehaviorFullScreenNone,
|
|
||||||
},
|
|
||||||
Windows: application.WindowsWindow{
|
|
||||||
Theme: application.Dark,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
s.installProgress.OnWindowEvent(events.Common.WindowClosing, func(_ *application.WindowEvent) {
|
s.installProgress.OnWindowEvent(events.Common.WindowClosing, func(_ *application.WindowEvent) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.installProgress = nil
|
s.installProgress = nil
|
||||||
|
|||||||
Reference in New Issue
Block a user