From 657d2e3e8cef091d4c3cbc60bf80ad36a24dfdf2 Mon Sep 17 00:00:00 2001
From: jeresrc
Date: Sat, 28 Feb 2026 21:42:40 -0300
Subject: [PATCH] feat(web): add dedicated settings sidebar and billing entry
---
.../dashboard/settings/billing/page.tsx | 25 +++++
.../s/[subdomain]/dashboard/settings/page.tsx | 60 +++++-------
apps/web/components/icons/icon-billing.tsx | 29 ++++++
.../components/icons/icon-book-bookmark.tsx | 29 ++++++
.../components/icons/icon-chevron-left.tsx | 29 ++++++
.../dashboard/components/dashboard-shell.tsx | 44 ++++++---
.../dashboard/components/settings-sidebar.tsx | 94 +++++++++++++++++++
apps/web/features/dashboard/dashboard-nav.ts | 3 +-
.../features/dashboard/lib/settings-nav.ts | 73 ++++++++++++++
9 files changed, 336 insertions(+), 50 deletions(-)
create mode 100644 apps/web/app/s/[subdomain]/dashboard/settings/billing/page.tsx
create mode 100644 apps/web/components/icons/icon-billing.tsx
create mode 100644 apps/web/components/icons/icon-book-bookmark.tsx
create mode 100644 apps/web/components/icons/icon-chevron-left.tsx
create mode 100644 apps/web/features/dashboard/components/settings-sidebar.tsx
create mode 100644 apps/web/features/dashboard/lib/settings-nav.ts
diff --git a/apps/web/app/s/[subdomain]/dashboard/settings/billing/page.tsx b/apps/web/app/s/[subdomain]/dashboard/settings/billing/page.tsx
new file mode 100644
index 0000000..21a4468
--- /dev/null
+++ b/apps/web/app/s/[subdomain]/dashboard/settings/billing/page.tsx
@@ -0,0 +1,25 @@
+export default function DashboardBillingSettingsPage() {
+ return (
+
+
+
+ Settings
+
+
+ Billing
+
+
+ Manage your plan, payment method, invoices, and billing details.
+
+
+
+
+
Billing dashboard
+
+ Billing controls will appear here next. For now, contact support to
+ update subscription details.
+
+
+
+ );
+}
diff --git a/apps/web/app/s/[subdomain]/dashboard/settings/page.tsx b/apps/web/app/s/[subdomain]/dashboard/settings/page.tsx
index cdc34d0..cea5104 100644
--- a/apps/web/app/s/[subdomain]/dashboard/settings/page.tsx
+++ b/apps/web/app/s/[subdomain]/dashboard/settings/page.tsx
@@ -1,29 +1,6 @@
import Link from "next/link";
-import { IconGlobe } from "@/components/icons/icon-globe";
-import { IconPeople } from "@/components/icons/icon-people";
-import { IconRoadmap } from "@/components/icons/icon-roadmap";
-
-const settingsItems = [
- {
- href: "/dashboard/settings/custom-domain",
- title: "Custom domain",
- description: "Connect and verify your domain.",
- icon: IconGlobe,
- },
- {
- href: "/dashboard/settings/team",
- title: "Team",
- description: "Invite members and manage permissions.",
- icon: IconPeople,
- },
- {
- href: "/dashboard/settings/feedback-roadmap",
- title: "Feedback & roadmap",
- description: "Manage tags, boards, statuses, and public board behavior.",
- icon: IconRoadmap,
- },
-];
+import { settingsNavGroups } from "~/dashboard/lib/settings-nav";
export default function DashboardSettingsPage() {
return (
@@ -40,19 +17,30 @@ export default function DashboardSettingsPage() {
-
- {settingsItems.map((item) => (
-
-
-
- {item.title}
+
+ {settingsNavGroups.map((group) => (
+
+
+ {group.label}
- {item.description}
-
+
+ {group.items.map((item) => (
+
+
+
+ {item.label}
+
+
+ {item.description}
+
+
+ ))}
+
+
))}
diff --git a/apps/web/components/icons/icon-billing.tsx b/apps/web/components/icons/icon-billing.tsx
new file mode 100644
index 0000000..e527ea6
--- /dev/null
+++ b/apps/web/components/icons/icon-billing.tsx
@@ -0,0 +1,29 @@
+import * as React from "react";
+import type { SVGProps } from "react";
+
+type IconBillingProps = SVGProps
& {
+ color?: string;
+};
+
+export function IconBilling({
+ color = "currentColor",
+ ...props
+}: IconBillingProps) {
+ return (
+
+ );
+}
diff --git a/apps/web/components/icons/icon-book-bookmark.tsx b/apps/web/components/icons/icon-book-bookmark.tsx
new file mode 100644
index 0000000..727307b
--- /dev/null
+++ b/apps/web/components/icons/icon-book-bookmark.tsx
@@ -0,0 +1,29 @@
+import * as React from "react";
+import type { SVGProps } from "react";
+
+type IconBookBookmarkProps = SVGProps & {
+ color?: string;
+};
+
+export function IconBookBookmark({
+ color = "currentColor",
+ ...props
+}: IconBookBookmarkProps) {
+ return (
+
+ );
+}
diff --git a/apps/web/components/icons/icon-chevron-left.tsx b/apps/web/components/icons/icon-chevron-left.tsx
new file mode 100644
index 0000000..b8e272e
--- /dev/null
+++ b/apps/web/components/icons/icon-chevron-left.tsx
@@ -0,0 +1,29 @@
+import * as React from "react";
+import type { SVGProps } from "react";
+
+type IconChevronLeftProps = SVGProps & {
+ color?: string;
+};
+
+export function IconChevronLeft({
+ color = "currentColor",
+ ...props
+}: IconChevronLeftProps) {
+ return (
+
+ );
+}
diff --git a/apps/web/features/dashboard/components/dashboard-shell.tsx b/apps/web/features/dashboard/components/dashboard-shell.tsx
index 5e2b244..29507bf 100644
--- a/apps/web/features/dashboard/components/dashboard-shell.tsx
+++ b/apps/web/features/dashboard/components/dashboard-shell.tsx
@@ -4,9 +4,11 @@ import { Menu01Icon } from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { AnimatePresence, motion } from "motion/react";
import { useState } from "react";
+import { usePathname } from "next/navigation";
import { Button } from "@/components/ui/button";
import { DashboardSidebar } from "~/dashboard/components/dashboard-sidebar";
+import { SettingsSidebar } from "~/dashboard/components/settings-sidebar";
export interface DashboardWorkspaceOption {
connectedDomain?: string | null;
@@ -41,6 +43,11 @@ function DashboardShellRoot({
user,
}: DashboardShellProps) {
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
+ const pathname = usePathname();
+ const normalizedPath = pathname.replace(/^\/s\/[^/]+/, "");
+ const isSettingsRoute =
+ normalizedPath === "/dashboard/settings" ||
+ normalizedPath.startsWith("/dashboard/settings/");
return (
-
+ {isSettingsRoute ? (
+
+ ) : (
+
+ )}
@@ -95,13 +106,20 @@ function DashboardShellRoot({
exit={{ x: -28, opacity: 0 }}
transition={{ duration: 0.2, ease: "easeOut" }}
>
- setMobileSidebarOpen(false)}
- organizationName={organizationName}
- workspaces={workspaces}
- user={user}
- />
+ {isSettingsRoute ? (
+ setMobileSidebarOpen(false)}
+ />
+ ) : (
+ setMobileSidebarOpen(false)}
+ organizationName={organizationName}
+ workspaces={workspaces}
+ user={user}
+ />
+ )}
>
) : null}
diff --git a/apps/web/features/dashboard/components/settings-sidebar.tsx b/apps/web/features/dashboard/components/settings-sidebar.tsx
new file mode 100644
index 0000000..3de02fe
--- /dev/null
+++ b/apps/web/features/dashboard/components/settings-sidebar.tsx
@@ -0,0 +1,94 @@
+"use client";
+
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+
+import { IconChevronLeft } from "@/components/icons/icon-chevron-left";
+import { cn } from "@/lib/utils";
+import { settingsNavGroups } from "~/dashboard/lib/settings-nav";
+
+interface SettingsSidebarProps {
+ className?: string;
+ onNavigate?: () => void;
+}
+
+function isActive(pathname: string, href: string) {
+ const normalizedPath = pathname.replace(/^\/s\/[^/]+/, "");
+ return normalizedPath === href || normalizedPath.startsWith(`${href}/`);
+}
+
+export function SettingsSidebar({
+ className,
+ onNavigate,
+}: SettingsSidebarProps) {
+ const pathname = usePathname();
+
+ return (
+
+ );
+}
diff --git a/apps/web/features/dashboard/dashboard-nav.ts b/apps/web/features/dashboard/dashboard-nav.ts
index 3da2d88..369a5aa 100644
--- a/apps/web/features/dashboard/dashboard-nav.ts
+++ b/apps/web/features/dashboard/dashboard-nav.ts
@@ -1,6 +1,7 @@
import type { ComponentType, SVGProps } from "react";
import { IconAssistant } from "@/components/icons/icon-assistant";
+import { IconBookBookmark } from "@/components/icons/icon-book-bookmark";
import { IconHelp } from "@/components/icons/icon-help";
import { IconInbox } from "@/components/icons/icon-inbox";
import { IconMap } from "@/components/icons/icon-map";
@@ -74,7 +75,7 @@ export const feedbackNavItems: DashboardNavItem[] = [
id: "changelog",
href: "/dashboard/changelog",
iconType: "svg",
- icon: IconMap,
+ icon: IconBookBookmark,
label: "Changelog",
},
{
diff --git a/apps/web/features/dashboard/lib/settings-nav.ts b/apps/web/features/dashboard/lib/settings-nav.ts
new file mode 100644
index 0000000..7907ade
--- /dev/null
+++ b/apps/web/features/dashboard/lib/settings-nav.ts
@@ -0,0 +1,73 @@
+import type { ComponentType, SVGProps } from "react";
+
+import { IconBilling } from "@/components/icons/icon-billing";
+import { IconGlobe } from "@/components/icons/icon-globe";
+import { IconPeople } from "@/components/icons/icon-people";
+import { IconRoadmap } from "@/components/icons/icon-roadmap";
+
+type SvgIconComponent = ComponentType>;
+
+export interface SettingsNavItem {
+ description: string;
+ href: string;
+ icon: SvgIconComponent;
+ id: string;
+ label: string;
+}
+
+export interface SettingsNavGroup {
+ id: string;
+ items: SettingsNavItem[];
+ label: string;
+}
+
+const workspaceSettingsNavItems: SettingsNavItem[] = [
+ {
+ id: "custom-domains",
+ href: "/dashboard/settings/custom-domain",
+ label: "Custom domains",
+ description: "Connect and verify your domain.",
+ icon: IconGlobe,
+ },
+ {
+ id: "feedback-roadmap",
+ href: "/dashboard/settings/feedback-roadmap",
+ label: "Feedback & Roadmap",
+ description: "Manage tags, boards, statuses, and public board behavior.",
+ icon: IconRoadmap,
+ },
+ {
+ id: "billing",
+ href: "/dashboard/settings/billing",
+ label: "Billing",
+ description: "Manage plan, invoices, and billing contact details.",
+ icon: IconBilling,
+ },
+];
+
+const administrationSettingsNavItems: SettingsNavItem[] = [
+ {
+ id: "team",
+ href: "/dashboard/settings/team",
+ label: "Team",
+ description: "Invite members and manage permissions.",
+ icon: IconPeople,
+ },
+];
+
+export const settingsNavGroups: SettingsNavGroup[] = [
+ {
+ id: "workspace",
+ label: "Workspace",
+ items: workspaceSettingsNavItems,
+ },
+ {
+ id: "administration",
+ label: "Administration",
+ items: administrationSettingsNavItems,
+ },
+];
+
+export const settingsNavItems: SettingsNavItem[] = settingsNavGroups.flatMap(
+ (group) => group.items,
+);