) {
function PopoverDescription({
className,
...props
-}: React.ComponentProps<"p">) {
+}: React.ComponentProps
) {
return (
- ) {
return (
-
+
+
+
);
}
diff --git a/packages/vitnode/src/components/ui/scroll-area.tsx b/packages/vitnode/src/components/ui/scroll-area.tsx
index 0e5fa4c6f..f9b611878 100644
--- a/packages/vitnode/src/components/ui/scroll-area.tsx
+++ b/packages/vitnode/src/components/ui/scroll-area.tsx
@@ -1,6 +1,6 @@
"use client";
-import { ScrollArea as ScrollAreaPrimitive } from "radix-ui";
+import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -20,7 +20,9 @@ function ScrollArea({
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
data-slot="scroll-area-viewport"
>
- {children}
+
+ {children}
+
@@ -32,23 +34,22 @@ function ScrollBar({
className,
orientation = "vertical",
...props
-}: React.ComponentProps) {
+}: React.ComponentProps) {
return (
-
-
-
+
);
}
diff --git a/packages/vitnode/src/components/ui/select.tsx b/packages/vitnode/src/components/ui/select.tsx
index cd9bcc8ba..0304a2f70 100644
--- a/packages/vitnode/src/components/ui/select.tsx
+++ b/packages/vitnode/src/components/ui/select.tsx
@@ -1,7 +1,7 @@
"use client";
+import { Select as SelectPrimitive } from "@base-ui/react/select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
-import { Select as SelectPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -50,9 +50,11 @@ function SelectTrigger({
{...props}
>
{children}
-
-
-
+
+ }
+ />
);
}
@@ -60,37 +62,47 @@ function SelectTrigger({
function SelectContent({
className,
children,
- position = "item-aligned",
- align = "center",
+ side = "bottom",
+ sideOffset = 4,
+ align = "start",
+ alignOffset = 0,
+ alignItemWithTrigger = false,
+ anchor,
...props
-}: React.ComponentProps) {
+}: Pick<
+ React.ComponentProps,
+ | "align"
+ | "alignItemWithTrigger"
+ | "alignOffset"
+ | "anchor"
+ | "side"
+ | "sideOffset"
+> &
+ React.ComponentProps) {
return (
-
-
{children}
-
+
-
+
);
}
@@ -98,9 +110,9 @@ function SelectContent({
function SelectLabel({
className,
...props
-}: React.ComponentProps) {
+}: React.ComponentProps) {
return (
- ) {
+}: React.ComponentProps) {
return (
-
-
+
);
}
function SelectScrollDownButton({
className,
...props
-}: React.ComponentProps) {
+}: React.ComponentProps) {
return (
-
-
+
);
}
diff --git a/packages/vitnode/src/components/ui/separator.tsx b/packages/vitnode/src/components/ui/separator.tsx
index bf810bc4b..f012033de 100644
--- a/packages/vitnode/src/components/ui/separator.tsx
+++ b/packages/vitnode/src/components/ui/separator.tsx
@@ -1,6 +1,6 @@
"use client";
-import { Separator as SeparatorPrimitive } from "radix-ui";
+import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -8,17 +8,15 @@ import { cn } from "@/lib/utils";
function Separator({
className,
orientation = "horizontal",
- decorative = true,
...props
-}: React.ComponentProps) {
+}: React.ComponentProps) {
return (
-
diff --git a/packages/vitnode/src/components/ui/sheet.tsx b/packages/vitnode/src/components/ui/sheet.tsx
index 8a5dd7241..f86a50693 100644
--- a/packages/vitnode/src/components/ui/sheet.tsx
+++ b/packages/vitnode/src/components/ui/sheet.tsx
@@ -1,43 +1,34 @@
"use client";
+import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
import { XIcon } from "lucide-react";
import { useTranslations } from "next-intl";
-import { Dialog as SheetPrimitive } from "radix-ui";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
-function Sheet({ ...props }: React.ComponentProps) {
+function Sheet({ ...props }: SheetPrimitive.Root.Props) {
return ;
}
-function SheetTrigger({
- ...props
-}: React.ComponentProps) {
+function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
return ;
}
-function SheetClose({
- ...props
-}: React.ComponentProps) {
+function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
return ;
}
-function SheetPortal({
- ...props
-}: React.ComponentProps) {
+function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
return ;
}
-function SheetOverlay({
- className,
- ...props
-}: React.ComponentProps) {
+function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
return (
- & {
+}: SheetPrimitive.Popup.Props & {
showCloseButton?: boolean;
side?: "bottom" | "left" | "right" | "top";
}) {
@@ -61,9 +52,9 @@ function SheetContent({
return (
-
{children}
{showCloseButton && (
-
-
+
+ }
+ >
+
)}
-
+
);
}
@@ -108,10 +98,7 @@ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
);
}
-function SheetTitle({
- className,
- ...props
-}: React.ComponentProps) {
+function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
return (
) {
+}: SheetPrimitive.Description.Props) {
return (
) {
function SidebarGroupLabel({
className,
- asChild = false,
+ render,
...props
-}: React.ComponentProps<"div"> & { asChild?: boolean }) {
- const Comp = asChild ? Slot.Root : "div";
-
- return (
- svg]:size-4 [&>svg]:shrink-0",
- className,
- )}
- data-sidebar="group-label"
- data-slot="sidebar-group-label"
- {...props}
- />
- );
+}: React.ComponentProps<"div"> & useRender.ComponentProps<"div">) {
+ return useRender({
+ defaultTagName: "div",
+ props: mergeProps<"div">(
+ {
+ className: cn(
+ "text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
+ className,
+ ),
+ },
+ props,
+ ),
+ render,
+ state: {
+ slot: "sidebar-group-label",
+ sidebar: "group-label",
+ },
+ });
}
function SidebarGroupAction({
className,
- asChild = false,
+ render,
...props
-}: React.ComponentProps<"button"> & { asChild?: boolean }) {
- const Comp = asChild ? Slot.Root : "button";
-
- return (
- svg]:size-4 [&>svg]:shrink-0",
- className,
- )}
- data-sidebar="group-action"
- data-slot="sidebar-group-action"
- {...props}
- />
- );
+}: React.ComponentProps<"button"> & useRender.ComponentProps<"button">) {
+ return useRender({
+ defaultTagName: "button",
+ props: mergeProps<"button">(
+ {
+ className: cn(
+ "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute end-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
+ className,
+ ),
+ },
+ props,
+ ),
+ render,
+ state: {
+ slot: "sidebar-group-action",
+ sidebar: "group-action",
+ },
+ });
}
function SidebarGroupContent({
@@ -494,7 +503,7 @@ const sidebarMenuButtonVariants = cva(
);
function SidebarMenuButton({
- asChild = false,
+ render,
isActive = false,
variant = "default",
size = "default",
@@ -502,24 +511,29 @@ function SidebarMenuButton({
className,
...props
}: React.ComponentProps<"button"> &
+ useRender.ComponentProps<"button"> &
VariantProps & {
- asChild?: boolean;
isActive?: boolean;
tooltip?: React.ComponentProps | string;
}) {
- const Comp = asChild ? Slot.Root : "button";
const { isMobile, state } = useSidebar();
- const button = (
-
- );
+ const button = useRender({
+ defaultTagName: "button",
+ props: mergeProps<"button">(
+ {
+ className: cn(sidebarMenuButtonVariants({ variant, size }), className),
+ },
+ props,
+ ),
+ render: !tooltip ? render : ,
+ state: {
+ slot: "sidebar-menu-button",
+ sidebar: "menu-button",
+ size,
+ active: isActive,
+ },
+ });
if (!tooltip) {
return button;
@@ -534,7 +548,7 @@ function SidebarMenuButton({
return (
-
+ {button}
& {
- asChild?: boolean;
- showOnHover?: boolean;
-}) {
- const Comp = asChild ? Slot.Root : "button";
-
- return (
- svg]:size-4 [&>svg]:shrink-0",
- showOnHover &&
- "peer-data-active/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 md:opacity-0",
- className,
- )}
- data-sidebar="menu-action"
- data-slot="sidebar-menu-action"
- {...props}
- />
- );
+}: React.ComponentProps<"button"> &
+ useRender.ComponentProps<"button"> & {
+ showOnHover?: boolean;
+ }) {
+ return useRender({
+ defaultTagName: "button",
+ props: mergeProps<"button">(
+ {
+ className: cn(
+ "text-sidebar-foreground ring-sidebar-ring peer-hover/menu-button:text-sidebar-accent-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute end-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
+ showOnHover &&
+ "peer-data-active/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 md:opacity-0",
+ className,
+ ),
+ },
+ props,
+ ),
+ render,
+ state: {
+ slot: "sidebar-menu-action",
+ sidebar: "menu-action",
+ },
+ });
}
function SidebarMenuBadge({
@@ -656,31 +674,35 @@ function SidebarMenuSubItem({
}
function SidebarMenuSubButton({
- asChild = false,
+ render,
size = "md",
isActive = false,
className,
...props
-}: React.ComponentProps<"a"> & {
- asChild?: boolean;
- isActive?: boolean;
- size?: "md" | "sm";
-}) {
- const Comp = asChild ? Slot.Root : "a";
-
- return (
- svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden group-data-[collapsible=icon]:hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-sm data-[size=sm]:text-xs rtl:translate-x-px [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
- className,
- )}
- data-active={isActive}
- data-sidebar="menu-sub-button"
- data-size={size}
- data-slot="sidebar-menu-sub-button"
- {...props}
- />
- );
+}: React.ComponentProps<"a"> &
+ useRender.ComponentProps<"a"> & {
+ isActive?: boolean;
+ size?: "md" | "sm";
+ }) {
+ return useRender({
+ defaultTagName: "a",
+ props: mergeProps<"a">(
+ {
+ className: cn(
+ "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden group-data-[collapsible=icon]:hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-sm data-[size=sm]:text-xs rtl:translate-x-px [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+ className,
+ ),
+ },
+ props,
+ ),
+ render,
+ state: {
+ slot: "sidebar-menu-sub-button",
+ sidebar: "menu-sub-button",
+ size,
+ active: isActive,
+ },
+ });
}
export {
diff --git a/packages/vitnode/src/components/ui/slider.tsx b/packages/vitnode/src/components/ui/slider.tsx
index b46384faf..3920af6f9 100644
--- a/packages/vitnode/src/components/ui/slider.tsx
+++ b/packages/vitnode/src/components/ui/slider.tsx
@@ -1,6 +1,6 @@
"use client";
-import { Slider as SliderPrimitive } from "radix-ui";
+import { Slider as SliderPrimitive } from "@base-ui/react/slider";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -26,7 +26,7 @@ function Slider({
return (
-
-
-
- {Array.from({ length: _values.length }, (_, index) => (
-
- ))}
+
+
+
+ {Array.from({ length: _values.length }, (_, index) => (
+
+ ))}
+
);
}
diff --git a/packages/vitnode/src/components/ui/switch.tsx b/packages/vitnode/src/components/ui/switch.tsx
index f80891cb1..e9302018b 100644
--- a/packages/vitnode/src/components/ui/switch.tsx
+++ b/packages/vitnode/src/components/ui/switch.tsx
@@ -1,6 +1,6 @@
"use client";
-import { Switch as SwitchPrimitive } from "radix-ui";
+import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
import * as React from "react";
import { cn } from "@/lib/utils";
diff --git a/packages/vitnode/src/components/ui/tabs.tsx b/packages/vitnode/src/components/ui/tabs.tsx
index b5d815ea1..d583cb4c8 100644
--- a/packages/vitnode/src/components/ui/tabs.tsx
+++ b/packages/vitnode/src/components/ui/tabs.tsx
@@ -1,7 +1,7 @@
"use client";
+import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
import { cva, type VariantProps } from "class-variance-authority";
-import { Tabs as TabsPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
@@ -19,6 +19,7 @@ function Tabs({
)}
data-orientation={orientation}
data-slot="tabs"
+ orientation={orientation}
{...props}
/>
);
@@ -58,9 +59,9 @@ function TabsList({
function TabsTrigger({
className,
...props
-}: React.ComponentProps) {
+}: React.ComponentProps) {
return (
- ) {
+}: React.ComponentProps) {
return (
- mql.removeEventListener("change", onChange);
diff --git a/packages/vitnode/src/lib/utils.ts b/packages/vitnode/src/lib/utils.ts
index 365058ceb..9c24333fc 100644
--- a/packages/vitnode/src/lib/utils.ts
+++ b/packages/vitnode/src/lib/utils.ts
@@ -4,3 +4,6 @@ import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
+
+export const normalizeUrl = (url: string) =>
+ url.endsWith("/") && url.length > 1 ? url.slice(0, -1) : url;
diff --git a/packages/vitnode/src/locales/en.json b/packages/vitnode/src/locales/en.json
index ba4d07844..f84da55e7 100644
--- a/packages/vitnode/src/locales/en.json
+++ b/packages/vitnode/src/locales/en.json
@@ -99,8 +99,11 @@
}
},
"user_bar": {
+ "my_profile": "My Profile",
"log_out": "Log out",
- "admin_cp": "Admin CP"
+ "mod_cp": "Moderator CP",
+ "admin_cp": "Admin CP",
+ "settings": "Settings"
}
},
"auth": {
@@ -204,6 +207,14 @@
"title": "Password changed successfully",
"desc": "You can now log in with your new password."
}
+ },
+ "settings": {
+ "title": "Settings",
+ "desc": "Manage your profile, security, and account preferences.",
+ "nav": {
+ "overview": "Overview",
+ "security": "Security"
+ }
}
}
},
@@ -264,8 +275,9 @@
"user": "User",
"createdAt": "Created At",
"emailNotVerified": "Email Not Verified",
- "view": "View profile",
+ "edit": "Edit profile",
"roles": "Roles",
+ "email": "Email",
"searchPlaceholder": "Search users by email or username...",
"noResults": {
"title": "No users found",
@@ -276,7 +288,26 @@
"title": "User Profile",
"joined": "Joined",
"emailNotVerified": "Email Not Verified",
- "coverPlaceholder": "No cover image"
+ "coverPlaceholder": "No cover image",
+ "editCover": "Edit cover image",
+ "editAvatar": "Edit avatar",
+ "editName": "Edit username",
+ "editEmail": "Edit email",
+ "uploadImage": "Upload image",
+ "removeImage": "Remove image",
+ "goToProfile": "Go to Public Profile",
+ "updateSuccess": "Profile updated.",
+ "editNameCode": "Edit name code",
+ "editNameCodeDesc": "The name code is the unique handle used in this user's profile URL and @mentions.",
+ "editNameCodeWarningTitle": "This change breaks existing links",
+ "editNameCodeWarning": "Changing the name code will break existing @mention links and change the public profile URL. Anyone visiting the old link will no longer reach this profile.",
+ "newNameCode": "New name code",
+ "confirmNameCode": "Type the current name code () to confirm",
+ "nameCodeInvalid": "Only letters, numbers, and hyphens are allowed.",
+ "nameCodeSame": "The new name code must be different from the current one.",
+ "nameCodeConfirmMismatch": "This does not match the current name code.",
+ "nameCodeExists": "This name code is already taken.",
+ "saveNameCode": "Change name code"
},
"verify_email": {
"label": "Verify Email",
diff --git a/packages/vitnode/src/routes/breadcrumb/main/[...rest]/page.tsx b/packages/vitnode/src/routes/breadcrumb/main/[...rest]/page.tsx
index 8e26f4953..e66aa3f96 100644
--- a/packages/vitnode/src/routes/breadcrumb/main/[...rest]/page.tsx
+++ b/packages/vitnode/src/routes/breadcrumb/main/[...rest]/page.tsx
@@ -1,17 +1,3 @@
-import { BreadcrumbMain } from "@/views/breadcrumb/breadcrumb-main";
-
-// Generic catch-all breadcrumb for public pages: humanizes URL segments.
-// Specific slot folders (login, register, …) override it with translated labels.
-//
-// NOTE: must live at the `@breadcrumb` slot ROOT (not under a `(plugins)` route
-// group) — a parallel-route slot only matches `children` sharing its route-group
-// structure, so a nested catch-all would miss pages outside that group.
-export default async function BreadcrumbSlot({
- params,
-}: {
- params: Promise<{ rest?: string[] }>;
-}) {
- const { rest } = await params;
-
- return ;
+export default function BreadcrumbSlot() {
+ return null;
}
diff --git a/packages/vitnode/src/routes/breadcrumb/main/settings/overview/page.tsx b/packages/vitnode/src/routes/breadcrumb/main/settings/overview/page.tsx
new file mode 100644
index 000000000..cde3a6cb3
--- /dev/null
+++ b/packages/vitnode/src/routes/breadcrumb/main/settings/overview/page.tsx
@@ -0,0 +1,17 @@
+import { getTranslations } from "next-intl/server";
+
+import { BreadcrumbMain } from "@/views/breadcrumb/breadcrumb-main";
+
+export default async function BreadcrumbSlot() {
+ const t = await getTranslations("core.auth.settings");
+
+ return (
+
+ );
+}
diff --git a/packages/vitnode/src/routes/breadcrumb/main/settings/page.tsx b/packages/vitnode/src/routes/breadcrumb/main/settings/page.tsx
new file mode 100644
index 000000000..8e057b504
--- /dev/null
+++ b/packages/vitnode/src/routes/breadcrumb/main/settings/page.tsx
@@ -0,0 +1,14 @@
+import { getTranslations } from "next-intl/server";
+
+import { BreadcrumbMain } from "@/views/breadcrumb/breadcrumb-main";
+
+export default async function BreadcrumbSlot() {
+ const t = await getTranslations("core.auth.settings");
+
+ return (
+
+ );
+}
diff --git a/packages/vitnode/src/routes/breadcrumb/main/settings/security/page.tsx b/packages/vitnode/src/routes/breadcrumb/main/settings/security/page.tsx
new file mode 100644
index 000000000..d2f9b6898
--- /dev/null
+++ b/packages/vitnode/src/routes/breadcrumb/main/settings/security/page.tsx
@@ -0,0 +1,17 @@
+import { getTranslations } from "next-intl/server";
+
+import { BreadcrumbMain } from "@/views/breadcrumb/breadcrumb-main";
+
+export default async function BreadcrumbSlot() {
+ const t = await getTranslations("core.auth.settings");
+
+ return (
+
+ );
+}
diff --git a/packages/vitnode/src/routes/main/settings/layout.tsx b/packages/vitnode/src/routes/main/settings/layout.tsx
new file mode 100644
index 000000000..68695a6e5
--- /dev/null
+++ b/packages/vitnode/src/routes/main/settings/layout.tsx
@@ -0,0 +1,16 @@
+import type { Metadata } from "next/dist/types";
+
+import { LayoutSettings } from "@/views/auth/settings/layout";
+
+export const metadata: Metadata = {
+ robots: {
+ index: false,
+ follow: false,
+ },
+};
+
+export default function Layout(
+ props: React.ComponentProps,
+) {
+ return ;
+}
diff --git a/packages/vitnode/src/routes/main/settings/overview/page.tsx b/packages/vitnode/src/routes/main/settings/overview/page.tsx
new file mode 100644
index 000000000..a1d547956
--- /dev/null
+++ b/packages/vitnode/src/routes/main/settings/overview/page.tsx
@@ -0,0 +1,15 @@
+import { getTranslations } from "next-intl/server";
+
+import { OverviewSettings } from "@/views/auth/settings/overview/overview";
+
+export const generateMetadata = async () => {
+ const t = await getTranslations("core.auth.settings");
+
+ return {
+ title: `${t("nav.overview")} - ${t("title")}`,
+ };
+};
+
+export default function Page() {
+ return ;
+}
diff --git a/packages/vitnode/src/routes/main/settings/page.tsx b/packages/vitnode/src/routes/main/settings/page.tsx
new file mode 100644
index 000000000..a1d547956
--- /dev/null
+++ b/packages/vitnode/src/routes/main/settings/page.tsx
@@ -0,0 +1,15 @@
+import { getTranslations } from "next-intl/server";
+
+import { OverviewSettings } from "@/views/auth/settings/overview/overview";
+
+export const generateMetadata = async () => {
+ const t = await getTranslations("core.auth.settings");
+
+ return {
+ title: `${t("nav.overview")} - ${t("title")}`,
+ };
+};
+
+export default function Page() {
+ return ;
+}
diff --git a/packages/vitnode/src/routes/main/settings/security/page.tsx b/packages/vitnode/src/routes/main/settings/security/page.tsx
new file mode 100644
index 000000000..a49f5f05a
--- /dev/null
+++ b/packages/vitnode/src/routes/main/settings/security/page.tsx
@@ -0,0 +1,15 @@
+import { getTranslations } from "next-intl/server";
+
+import { SecuritySettings } from "@/views/auth/settings/security/security";
+
+export const generateMetadata = async () => {
+ const t = await getTranslations("core.auth.settings");
+
+ return {
+ title: `${t("nav.security")} - ${t("title")}`,
+ };
+};
+
+export default function Page() {
+ return ;
+}
diff --git a/packages/vitnode/src/views/admin/layouts/sidebar/nav/item.tsx b/packages/vitnode/src/views/admin/layouts/sidebar/nav/item.tsx
index 74dfc0c53..8ac1146fc 100644
--- a/packages/vitnode/src/views/admin/layouts/sidebar/nav/item.tsx
+++ b/packages/vitnode/src/views/admin/layouts/sidebar/nav/item.tsx
@@ -65,54 +65,58 @@ export const ItemNavAdmin = ({
if (!items.length) {
return (
-
- {
- if (isMobile) {
- toggleSidebar();
- }
- }}
- rel={isOpenInNewTab ? "noopener noreferrer" : undefined}
- target={isOpenInNewTab ? "_blank" : undefined}
- >
- {content}
-
+ {
+ if (isMobile) {
+ toggleSidebar();
+ }
+ }}
+ rel={isOpenInNewTab ? "noopener noreferrer" : undefined}
+ target={isOpenInNewTab ? "_blank" : undefined}
+ />
+ }
+ tooltip={title}
+ >
+ {content}
);
}
return (
-
-
-
+ }>
+
- {content}
-
-
-
+ />
+ }
+ >
+ {content}
+
+
-
-
- {items.map(item => {
- const isChildActive =
- normalizeUrl(pathname) === normalizeUrl(item.href);
+
+
+ {items.map(item => {
+ const isChildActive =
+ normalizeUrl(pathname) === normalizeUrl(item.href);
- return (
-
-
+ return (
+
+ {
@@ -124,16 +128,16 @@ export const ItemNavAdmin = ({
item.isOpenInNewTab ? "noopener noreferrer" : undefined
}
target={item.isOpenInNewTab ? "_blank" : undefined}
- >
- {item.title}
-
-
-
- );
- })}
-
-
-
+ />
+ }
+ >
+ {item.title}
+
+
+ );
+ })}
+
+
);
};
diff --git a/packages/vitnode/src/views/admin/layouts/user-bar/client.tsx b/packages/vitnode/src/views/admin/layouts/user-bar/client.tsx
index f50b4acf6..9fb7a2d45 100644
--- a/packages/vitnode/src/views/admin/layouts/user-bar/client.tsx
+++ b/packages/vitnode/src/views/admin/layouts/user-bar/client.tsx
@@ -32,22 +32,18 @@ export const ClientUserBarAdmin = ({
-
-
-
- {t("home_page")}
-
+ }>
+
+ {t("home_page")}
-
-
-
- {t("debug")}
-
+ }>
+
+ {t("debug")}
{
await logOutMutationApi({ isAdmin: true });
}}
diff --git a/packages/vitnode/src/views/admin/layouts/user-bar/user-bar.tsx b/packages/vitnode/src/views/admin/layouts/user-bar/user-bar.tsx
index cc96b238a..b203d6f01 100644
--- a/packages/vitnode/src/views/admin/layouts/user-bar/user-bar.tsx
+++ b/packages/vitnode/src/views/admin/layouts/user-bar/user-bar.tsx
@@ -20,14 +20,14 @@ export const UserBarAdmin = ({
>) => {
return (
-
-
+ }
+ >
+
diff --git a/packages/vitnode/src/views/admin/views/core/debug/system-logs/actions/more/more.tsx b/packages/vitnode/src/views/admin/views/core/debug/system-logs/actions/more/more.tsx
index ae36ad8ca..15532c8fd 100644
--- a/packages/vitnode/src/views/admin/views/core/debug/system-logs/actions/more/more.tsx
+++ b/packages/vitnode/src/views/admin/views/core/debug/system-logs/actions/more/more.tsx
@@ -31,10 +31,12 @@ export const MoreActionSystemLogs = (
return (