feat(shell): personalized sidebar + header with real user and company

- Layout split: (dashboard)/layout.tsx is now async server component that
  fetches active context and passes user/company to (dashboard)/dashboard-shell.tsx
  (client). Redirects to /onboarding if no tenant.
- AppSidebar:
  * Header shows 'İşletmem' + the active company name (companyName from
    tenant_settings), instead of mock 'ShadcnStore / Admin Dashboard'.
  * Nav rebuilt for our modules in Turkish: Genel bakış, Müşteriler,
    Hizmetler, Yazılımlarımız, Takvim, Görevler, Gelir/Gider, Faturalar,
    Çalışma alanı (with submenu), Profil, Plan.
  * Removed SidebarNotification (template promo widget).
  * Accepts user/company props (typed via ShellUser/ShellCompany).
- NavUser:
  * Real user name + email, no more 'ShadcnStore / store@example.com'.
  * Avatar shows initials from name in primary/10 tinted square.
  * Logout wired to signOutAction (server action) via useTransition.
  * Menu items localized (Profil, Plan & Faturalama, Bildirimler, Çıkış yap).
- SiteHeader:
  * Removed Blocks / Landing / GitHub external links (template demo links).
  * Shows company name with Building2 icon between sidebar trigger and
    search trigger.
  * Search trigger moved to right side next to ModeToggle.
- Dropped UpgradeToProButton from the shell (template promo).
- Deleted dead-code src/components/layouts/base-layout.tsx (unused alt
  layout that wasn't compatible with the new AppSidebar props).
This commit is contained in:
kovakmedya
2026-04-30 03:43:00 +03:00
parent 8a7742af1b
commit 0a280fd3a3
6 changed files with 301 additions and 458 deletions
+93
View File
@@ -0,0 +1,93 @@
"use client";
import React from "react";
import { AppSidebar } from "@/components/app-sidebar";
import { SiteHeader } from "@/components/site-header";
import { SiteFooter } from "@/components/site-footer";
import { SidebarProvider, SidebarInset } from "@/components/ui/sidebar";
import { ThemeCustomizer, ThemeCustomizerTrigger } from "@/components/theme-customizer";
import { useSidebarConfig } from "@/hooks/use-sidebar-config";
export type ShellUser = {
id: string;
name: string;
email: string;
};
export type ShellCompany = {
id: string;
name: string;
};
export function DashboardShell({
user,
company,
children,
}: {
user: ShellUser;
company: ShellCompany;
children: React.ReactNode;
}) {
const [themeCustomizerOpen, setThemeCustomizerOpen] = React.useState(false);
const { config } = useSidebarConfig();
return (
<SidebarProvider
style={
{
"--sidebar-width": "16rem",
"--sidebar-width-icon": "3rem",
"--header-height": "calc(var(--spacing) * 14)",
} as React.CSSProperties
}
className={config.collapsible === "none" ? "sidebar-none-mode" : ""}
>
{config.side === "left" ? (
<>
<AppSidebar
user={user}
company={company}
variant={config.variant}
collapsible={config.collapsible}
side={config.side}
/>
<SidebarInset>
<SiteHeader company={company} />
<div className="flex flex-1 flex-col">
<div className="@container/main flex flex-1 flex-col gap-2">
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">{children}</div>
</div>
</div>
<SiteFooter />
</SidebarInset>
</>
) : (
<>
<SidebarInset>
<SiteHeader company={company} />
<div className="flex flex-1 flex-col">
<div className="@container/main flex flex-1 flex-col gap-2">
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">{children}</div>
</div>
</div>
<SiteFooter />
</SidebarInset>
<AppSidebar
user={user}
company={company}
variant={config.variant}
collapsible={config.collapsible}
side={config.side}
/>
</>
)}
<ThemeCustomizerTrigger onClick={() => setThemeCustomizerOpen(true)} />
<ThemeCustomizer
open={themeCustomizerOpen}
onOpenChange={setThemeCustomizerOpen}
/>
</SidebarProvider>
);
}
+19 -68
View File
@@ -1,78 +1,29 @@
"use client";
import { redirect } from "next/navigation";
import React from "react";
import { AppSidebar } from "@/components/app-sidebar";
import { SiteHeader } from "@/components/site-header";
import { SiteFooter } from "@/components/site-footer";
import { SidebarProvider, SidebarInset } from "@/components/ui/sidebar";
import { ThemeCustomizer, ThemeCustomizerTrigger } from "@/components/theme-customizer";
import { UpgradeToProButton } from "@/components/upgrade-to-pro-button";
import { useSidebarConfig } from "@/hooks/use-sidebar-config";
import { getActiveContext } from "@/lib/appwrite/active-context";
import { DashboardShell } from "./dashboard-shell";
export default function DashboardLayout({
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const [themeCustomizerOpen, setThemeCustomizerOpen] = React.useState(false);
const { config } = useSidebarConfig();
const ctx = await getActiveContext();
if (!ctx) redirect("/onboarding");
const company = {
id: ctx.tenantId,
name: ctx.settings?.companyName ?? "Çalışma alanı",
};
const user = {
id: ctx.user.id,
name: ctx.user.name || ctx.user.email,
email: ctx.user.email,
};
return (
<SidebarProvider
style={{
"--sidebar-width": "16rem",
"--sidebar-width-icon": "3rem",
"--header-height": "calc(var(--spacing) * 14)",
} as React.CSSProperties}
className={config.collapsible === "none" ? "sidebar-none-mode" : ""}
>
{config.side === "left" ? (
<>
<AppSidebar
variant={config.variant}
collapsible={config.collapsible}
side={config.side}
/>
<SidebarInset>
<SiteHeader />
<div className="flex flex-1 flex-col">
<div className="@container/main flex flex-1 flex-col gap-2">
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
{children}
</div>
</div>
</div>
<SiteFooter />
</SidebarInset>
</>
) : (
<>
<SidebarInset>
<SiteHeader />
<div className="flex flex-1 flex-col">
<div className="@container/main flex flex-1 flex-col gap-2">
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
{children}
</div>
</div>
</div>
<SiteFooter />
</SidebarInset>
<AppSidebar
variant={config.variant}
collapsible={config.collapsible}
side={config.side}
/>
</>
)}
{/* Theme Customizer */}
<ThemeCustomizerTrigger onClick={() => setThemeCustomizerOpen(true)} />
<ThemeCustomizer
open={themeCustomizerOpen}
onOpenChange={setThemeCustomizerOpen}
/>
<UpgradeToProButton />
</SidebarProvider>
<DashboardShell user={user} company={company}>
{children}
</DashboardShell>
);
}