"use client"; import { useTemplateCopy } from "@/components/foundations/template-copy/template-copy"; import { RiAddFill, RiChatAiLine, RiCloseLine, RiDashboardLine, RiFilter3Fill, RiLayoutGridLine, RiLoginBoxLine, RiMenuLine, RiUserAddLine, } from "@remixicon/react"; import type { ComponentType } from "react"; import { usePathname } from "next/navigation"; import { useState, type ReactNode } from "react"; import { DashboardSidebar, type DashboardNavItem, } from "@/components/application/dashboard/dashboard-sidebar"; import { NotificationBell } from "@/components/application/app-shell/notification-bell"; import { ProOfferCard } from "@/components/application/app-shell/pro-offer-card"; import { Avatar } from "@/components/base/avatar/avatar"; import { Breadcrumb, BreadcrumbItem } from "@/components/base/breadcrumb/breadcrumb"; import { Button } from "@/components/base/buttons/button"; import { IconButton } from "@/components/base/buttons/icon-button"; import { cx } from "@/utils/cx"; /** * The starter's page frame: the sidebar, a drawer for phones, and a titled * content card. Every page of the starter except the chat renders inside it, * and the chat shares its navigation, so the sidebar only ever links to pages * the app actually has. * * On boardui.com the starter lives under /templates/chat-starter; in a * deployed copy it is the root. The base is read off the URL rather than * configured, so the same files run in both places untouched. */ export const STARTER_SITE_PREFIX = "/templates/chat-starter"; // Re-exported so the catalogue ships with the shell; the starter's pages are // copied into the public repo by path and can only import what a registry // item carries. export { CatalogTierFilter, ComponentsCatalog, type CatalogTier, } from "@/components/application/app-shell/components-catalog"; /** "" in a deployed starter, the site prefix on boardui.com. */ export function useStarterBase(): string { const pathname = usePathname() ?? ""; // Preserve an optional language prefix on the hosted preview. Installed // starters still use their normal root routes. return pathname.match(/^((?:\/[a-z]{2}(?:-[a-zA-Z]{2})?)?\/templates\/chat-starter)(?:\/|$)/)?.[1] ?? ""; } export function starterNav(base: string): DashboardNavItem[] { return [ { key: "chat", label: "Chat", icon: RiChatAiLine, href: base || "/" }, { key: "dashboard", label: "Dashboard", icon: RiDashboardLine, href: `${base}/dashboard` }, { key: "components", label: "Components and Blocks", icon: RiLayoutGridLine, href: `${base}/components` }, // Auth screens, as examples: full-page, so nothing is selected while on them. { key: "login", label: "Sign in", icon: RiLoginBoxLine, href: `${base}/login` }, { key: "signup", label: "Sign up", icon: RiUserAddLine, href: `${base}/signup` }, ]; } /** The nav key of the page at the current URL. */ export function useStarterSelected(): string { const pathname = usePathname() ?? ""; const base = useStarterBase(); const rest = pathname.slice(base.length); if (rest.startsWith("/dashboard")) return "dashboard"; if (rest.startsWith("/components")) return "components"; return "chat"; } type IconComponent = ComponentType<{ className?: string; "aria-hidden"?: boolean | "true" | "false" }>; /** * The page standard every BoardUI template follows, free or Pro: content sits * directly on the page background inside a 1300px column, so cards and tables * carry their own surfaces; above it a breadcrumb trail (team, member, page) * and the page heading. Pages pass what differs and get the rest. */ export function AppShell({ title, heading = title, icon: Icon = RiDashboardLine, actions, children, className, columnClassName, }: { /** The page's name, shown as the current breadcrumb item. */ title: string; /** The heading over the content. Defaults to the title. */ heading?: string; /** Icon on the current breadcrumb item. */ icon?: IconComponent; /** Header actions on the right; the Pro dashboard's set by default, `null` for none. */ actions?: ReactNode; children: ReactNode; className?: string; /** Overrides the 1300px content column, e.g. `max-w-[964px]` for the catalogue's grids. */ columnClassName?: string; }) { const localize = useTemplateCopy(); const [navOpen, setNavOpen] = useState(false); const items = starterNav(useStarterBase()); const selected = useStarterSelected(); return localize((
{/* Below lg the sidebar rides in as an overlay drawer. */} {navOpen && (
)}
Board team Mertcan {title}
setNavOpen((open) => !open)} className="lg:hidden" />

{heading}

{actions !== undefined ? actions : ( <> )}
{children}
)); }