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:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user