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, +);