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 { getActiveContext } from "@/lib/appwrite/active-context";
|
||||||
import { AppSidebar } from "@/components/app-sidebar";
|
import { DashboardShell } from "./dashboard-shell";
|
||||||
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";
|
|
||||||
|
|
||||||
export default function DashboardLayout({
|
export default async function DashboardLayout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const [themeCustomizerOpen, setThemeCustomizerOpen] = React.useState(false);
|
const ctx = await getActiveContext();
|
||||||
const { config } = useSidebarConfig();
|
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 (
|
return (
|
||||||
<SidebarProvider
|
<DashboardShell user={user} company={company}>
|
||||||
style={{
|
{children}
|
||||||
"--sidebar-width": "16rem",
|
</DashboardShell>
|
||||||
"--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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+119
-203
@@ -1,27 +1,24 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
LayoutPanelLeft,
|
Briefcase,
|
||||||
LayoutDashboard,
|
|
||||||
Mail,
|
|
||||||
CheckSquare,
|
|
||||||
MessageCircle,
|
|
||||||
Calendar,
|
Calendar,
|
||||||
Shield,
|
CheckSquare,
|
||||||
AlertTriangle,
|
|
||||||
Settings,
|
|
||||||
HelpCircle,
|
|
||||||
CreditCard,
|
CreditCard,
|
||||||
LayoutTemplate,
|
FileText,
|
||||||
|
LayoutDashboard,
|
||||||
|
Package,
|
||||||
|
Receipt,
|
||||||
|
Settings,
|
||||||
Users,
|
Users,
|
||||||
} from "lucide-react"
|
Wallet,
|
||||||
import Link from "next/link"
|
} from "lucide-react";
|
||||||
import { Logo } from "@/components/logo"
|
import Link from "next/link";
|
||||||
import { SidebarNotification } from "@/components/sidebar-notification"
|
|
||||||
|
|
||||||
import { NavMain } from "@/components/nav-main"
|
import { Logo } from "@/components/logo";
|
||||||
import { NavUser } from "@/components/nav-user"
|
import { NavMain } from "@/components/nav-main";
|
||||||
|
import { NavUser } from "@/components/nav-user";
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
@@ -30,186 +27,106 @@ import {
|
|||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from "@/components/ui/sidebar"
|
} from "@/components/ui/sidebar";
|
||||||
|
|
||||||
const data = {
|
import type { ShellCompany, ShellUser } from "@/app/(dashboard)/dashboard-shell";
|
||||||
user: {
|
|
||||||
name: "ShadcnStore",
|
const navGroups = [
|
||||||
email: "store@example.com",
|
{
|
||||||
avatar: "",
|
label: "Genel",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Genel bakış",
|
||||||
|
url: "/dashboard",
|
||||||
|
icon: LayoutDashboard,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
navGroups: [
|
{
|
||||||
{
|
label: "İşletme",
|
||||||
label: "Dashboards",
|
items: [
|
||||||
items: [
|
{
|
||||||
{
|
title: "Müşteriler",
|
||||||
title: "Dashboard 1",
|
url: "/customers",
|
||||||
url: "/dashboard",
|
icon: Users,
|
||||||
icon: LayoutDashboard,
|
},
|
||||||
},
|
{
|
||||||
{
|
title: "Hizmetler",
|
||||||
title: "Dashboard 2",
|
url: "/services",
|
||||||
url: "/dashboard-2",
|
icon: Briefcase,
|
||||||
icon: LayoutPanelLeft,
|
},
|
||||||
},
|
{
|
||||||
],
|
title: "Yazılımlarımız",
|
||||||
},
|
url: "/software",
|
||||||
{
|
icon: Package,
|
||||||
label: "Apps",
|
},
|
||||||
items: [
|
],
|
||||||
{
|
},
|
||||||
title: "Mail",
|
{
|
||||||
url: "/mail",
|
label: "Operasyon",
|
||||||
icon: Mail,
|
items: [
|
||||||
},
|
{
|
||||||
{
|
title: "Takvim",
|
||||||
title: "Tasks",
|
url: "/calendar",
|
||||||
url: "/tasks",
|
icon: Calendar,
|
||||||
icon: CheckSquare,
|
},
|
||||||
},
|
{
|
||||||
{
|
title: "Görevler",
|
||||||
title: "Chat",
|
url: "/tasks",
|
||||||
url: "/chat",
|
icon: CheckSquare,
|
||||||
icon: MessageCircle,
|
},
|
||||||
},
|
],
|
||||||
{
|
},
|
||||||
title: "Calendar",
|
{
|
||||||
url: "/calendar",
|
label: "Finans",
|
||||||
icon: Calendar,
|
items: [
|
||||||
},
|
{
|
||||||
{
|
title: "Gelir / Gider",
|
||||||
title: "Users",
|
url: "/finance",
|
||||||
url: "/users",
|
icon: Wallet,
|
||||||
icon: Users,
|
},
|
||||||
},
|
{
|
||||||
],
|
title: "Faturalar",
|
||||||
},
|
url: "/invoices",
|
||||||
{
|
icon: Receipt,
|
||||||
label: "Pages",
|
},
|
||||||
items: [
|
],
|
||||||
{
|
},
|
||||||
title: "Landing",
|
{
|
||||||
url: "/landing",
|
label: "Hesap",
|
||||||
target: "_blank",
|
items: [
|
||||||
icon: LayoutTemplate,
|
{
|
||||||
},
|
title: "Çalışma alanı",
|
||||||
{
|
url: "/settings/workspace",
|
||||||
title: "Auth Pages",
|
icon: Settings,
|
||||||
url: "#",
|
items: [
|
||||||
icon: Shield,
|
{ title: "Şirket bilgileri", url: "/settings/workspace" },
|
||||||
items: [
|
{ title: "Ekip üyeleri", url: "/settings/members" },
|
||||||
{
|
{ title: "Faturalama", url: "/settings/billing" },
|
||||||
title: "Sign In 1",
|
],
|
||||||
url: "/sign-in",
|
},
|
||||||
},
|
{
|
||||||
{
|
title: "Profil",
|
||||||
title: "Sign In 2",
|
url: "/settings/account",
|
||||||
url: "/sign-in-2",
|
icon: FileText,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Sign In 3",
|
title: "Plan",
|
||||||
url: "/sign-in-3",
|
url: "/pricing",
|
||||||
},
|
icon: CreditCard,
|
||||||
{
|
},
|
||||||
title: "Sign Up 1",
|
],
|
||||||
url: "/sign-up",
|
},
|
||||||
},
|
];
|
||||||
{
|
|
||||||
title: "Sign Up 2",
|
|
||||||
url: "/sign-up-2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Sign Up 3",
|
|
||||||
url: "/sign-up-3",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Forgot Password 1",
|
|
||||||
url: "/forgot-password",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Forgot Password 2",
|
|
||||||
url: "/forgot-password-2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Forgot Password 3",
|
|
||||||
url: "/forgot-password-3",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Errors",
|
|
||||||
url: "#",
|
|
||||||
icon: AlertTriangle,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: "Unauthorized",
|
|
||||||
url: "/errors/unauthorized",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Forbidden",
|
|
||||||
url: "/errors/forbidden",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Not Found",
|
|
||||||
url: "/errors/not-found",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Internal Server Error",
|
|
||||||
url: "/errors/internal-server-error",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Under Maintenance",
|
|
||||||
url: "/errors/under-maintenance",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Settings",
|
|
||||||
url: "#",
|
|
||||||
icon: Settings,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: "User Settings",
|
|
||||||
url: "/settings/user",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Account Settings",
|
|
||||||
url: "/settings/account",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Plans & Billing",
|
|
||||||
url: "/settings/billing",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Appearance",
|
|
||||||
url: "/settings/appearance",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Notifications",
|
|
||||||
url: "/settings/notifications",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Connections",
|
|
||||||
url: "/settings/connections",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "FAQs",
|
|
||||||
url: "/faqs",
|
|
||||||
icon: HelpCircle,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Pricing",
|
|
||||||
url: "/pricing",
|
|
||||||
icon: CreditCard,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
export function AppSidebar({
|
||||||
|
user,
|
||||||
|
company,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof Sidebar> & {
|
||||||
|
user: ShellUser;
|
||||||
|
company: ShellCompany;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Sidebar {...props}>
|
<Sidebar {...props}>
|
||||||
<SidebarHeader>
|
<SidebarHeader>
|
||||||
@@ -217,12 +134,12 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton size="lg" asChild>
|
<SidebarMenuButton size="lg" asChild>
|
||||||
<Link href="/dashboard">
|
<Link href="/dashboard">
|
||||||
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
|
<div className="bg-primary text-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
||||||
<Logo size={24} className="text-current" />
|
<Logo size={20} className="text-current" />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
<span className="truncate font-medium">ShadcnStore</span>
|
<span className="truncate font-medium">İşletmem</span>
|
||||||
<span className="truncate text-xs">Admin Dashboard</span>
|
<span className="text-muted-foreground truncate text-xs">{company.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
@@ -230,14 +147,13 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
{data.navGroups.map((group) => (
|
{navGroups.map((group) => (
|
||||||
<NavMain key={group.label} label={group.label} items={group.items} />
|
<NavMain key={group.label} label={group.label} items={group.items} />
|
||||||
))}
|
))}
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<SidebarNotification />
|
<NavUser user={user} />
|
||||||
<NavUser user={data.user} />
|
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { AppSidebar } from "@/components/app-sidebar"
|
|
||||||
import { SiteHeader } from "@/components/site-header"
|
|
||||||
import { SiteFooter } from "@/components/site-footer"
|
|
||||||
import { ThemeCustomizer, ThemeCustomizerTrigger } from "@/components/theme-customizer"
|
|
||||||
import { UpgradeToProButton } from "@/components/upgrade-to-pro-button"
|
|
||||||
import { useSidebarConfig } from "@/hooks/use-sidebar-config"
|
|
||||||
import {
|
|
||||||
SidebarInset,
|
|
||||||
SidebarProvider,
|
|
||||||
} from "@/components/ui/sidebar"
|
|
||||||
|
|
||||||
interface BaseLayoutProps {
|
|
||||||
children: React.ReactNode
|
|
||||||
title?: string
|
|
||||||
description?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function BaseLayout({ children, title, description }: BaseLayoutProps) {
|
|
||||||
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
|
|
||||||
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">
|
|
||||||
{title && (
|
|
||||||
<div className="px-4 lg:px-6">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<h1 className="text-2xl font-bold tracking-tight">{title}</h1>
|
|
||||||
{description && (
|
|
||||||
<p className="text-muted-foreground">{description}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{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">
|
|
||||||
{title && (
|
|
||||||
<div className="px-4 lg:px-6">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<h1 className="text-2xl font-bold tracking-tight">{title}</h1>
|
|
||||||
{description && (
|
|
||||||
<p className="text-muted-foreground">{description}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
+40
-33
@@ -1,15 +1,15 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
|
import { useTransition } from "react";
|
||||||
import {
|
import {
|
||||||
|
BellDot,
|
||||||
|
CircleUser,
|
||||||
CreditCard,
|
CreditCard,
|
||||||
EllipsisVertical,
|
EllipsisVertical,
|
||||||
LogOut,
|
LogOut,
|
||||||
BellDot,
|
} from "lucide-react";
|
||||||
CircleUser,
|
import Link from "next/link";
|
||||||
} from "lucide-react"
|
|
||||||
import Link from "next/link"
|
|
||||||
|
|
||||||
import { Logo } from "@/components/logo"
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -18,24 +18,33 @@ import {
|
|||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from "@/components/ui/dropdown-menu";
|
||||||
import {
|
import {
|
||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
useSidebar,
|
useSidebar,
|
||||||
} from "@/components/ui/sidebar"
|
} from "@/components/ui/sidebar";
|
||||||
|
import { signOutAction } from "@/lib/appwrite/auth-actions";
|
||||||
|
|
||||||
|
function initials(name: string) {
|
||||||
|
const parts = name.trim().split(/\s+/).slice(0, 2);
|
||||||
|
return parts.map((p) => p[0]?.toUpperCase() ?? "").join("") || "?";
|
||||||
|
}
|
||||||
|
|
||||||
export function NavUser({
|
export function NavUser({
|
||||||
user,
|
user,
|
||||||
}: {
|
}: {
|
||||||
user: {
|
user: { name: string; email: string };
|
||||||
name: string
|
|
||||||
email: string
|
|
||||||
avatar: string
|
|
||||||
}
|
|
||||||
}) {
|
}) {
|
||||||
const { isMobile } = useSidebar()
|
const { isMobile } = useSidebar();
|
||||||
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
|
const handleSignOut = () => {
|
||||||
|
startTransition(async () => {
|
||||||
|
await signOutAction();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
@@ -46,14 +55,12 @@ export function NavUser({
|
|||||||
size="lg"
|
size="lg"
|
||||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground cursor-pointer"
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground cursor-pointer"
|
||||||
>
|
>
|
||||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg">
|
<div className="bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg text-sm font-medium">
|
||||||
< Logo size={28} />
|
{initials(user.name)}
|
||||||
</div>
|
</div>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
<span className="truncate font-medium">{user.name}</span>
|
<span className="truncate font-medium">{user.name}</span>
|
||||||
<span className="text-muted-foreground truncate text-xs">
|
<span className="text-muted-foreground truncate text-xs">{user.email}</span>
|
||||||
{user.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<EllipsisVertical className="ml-auto size-4" />
|
<EllipsisVertical className="ml-auto size-4" />
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
@@ -66,14 +73,12 @@ export function NavUser({
|
|||||||
>
|
>
|
||||||
<DropdownMenuLabel className="p-0 font-normal">
|
<DropdownMenuLabel className="p-0 font-normal">
|
||||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||||
<div className="h-8 w-8 rounded-lg">
|
<div className="bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg text-sm font-medium">
|
||||||
< Logo size={28} />
|
{initials(user.name)}
|
||||||
</div>
|
</div>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
<span className="truncate font-medium">{user.name}</span>
|
<span className="truncate font-medium">{user.name}</span>
|
||||||
<span className="text-muted-foreground truncate text-xs">
|
<span className="text-muted-foreground truncate text-xs">{user.email}</span>
|
||||||
{user.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
@@ -82,32 +87,34 @@ export function NavUser({
|
|||||||
<DropdownMenuItem asChild className="cursor-pointer">
|
<DropdownMenuItem asChild className="cursor-pointer">
|
||||||
<Link href="/settings/account">
|
<Link href="/settings/account">
|
||||||
<CircleUser />
|
<CircleUser />
|
||||||
Account
|
Profil
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem asChild className="cursor-pointer">
|
<DropdownMenuItem asChild className="cursor-pointer">
|
||||||
<Link href="/settings/billing">
|
<Link href="/settings/billing">
|
||||||
<CreditCard />
|
<CreditCard />
|
||||||
Billing
|
Plan & Faturalama
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem asChild className="cursor-pointer">
|
<DropdownMenuItem asChild className="cursor-pointer">
|
||||||
<Link href="/settings/notifications">
|
<Link href="/settings/notifications">
|
||||||
<BellDot />
|
<BellDot />
|
||||||
Notifications
|
Bildirimler
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem asChild className="cursor-pointer">
|
<DropdownMenuItem
|
||||||
<Link href="/sign-in">
|
onClick={handleSignOut}
|
||||||
<LogOut />
|
disabled={isPending}
|
||||||
Log out
|
className="cursor-pointer"
|
||||||
</Link>
|
>
|
||||||
|
<LogOut />
|
||||||
|
{isPending ? "Çıkış yapılıyor..." : "Çıkış yap"}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Building2 } from "lucide-react";
|
||||||
import { Separator } from "@/components/ui/separator"
|
|
||||||
import { SidebarTrigger } from "@/components/ui/sidebar"
|
|
||||||
import { CommandSearch, SearchTrigger } from "@/components/command-search"
|
|
||||||
import { ModeToggle } from "@/components/mode-toggle"
|
|
||||||
|
|
||||||
export function SiteHeader() {
|
import { CommandSearch, SearchTrigger } from "@/components/command-search";
|
||||||
const [searchOpen, setSearchOpen] = React.useState(false)
|
import { ModeToggle } from "@/components/mode-toggle";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { SidebarTrigger } from "@/components/ui/sidebar";
|
||||||
|
|
||||||
|
import type { ShellCompany } from "@/app/(dashboard)/dashboard-shell";
|
||||||
|
|
||||||
|
export function SiteHeader({ company }: { company?: ShellCompany }) {
|
||||||
|
const [searchOpen, setSearchOpen] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const down = (e: KeyboardEvent) => {
|
const down = (e: KeyboardEvent) => {
|
||||||
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
setSearchOpen((open) => !open)
|
setSearchOpen((open) => !open);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
document.addEventListener("keydown", down)
|
document.addEventListener("keydown", down);
|
||||||
return () => document.removeEventListener("keydown", down)
|
return () => document.removeEventListener("keydown", down);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -31,45 +34,23 @@ export function SiteHeader() {
|
|||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
className="mx-2 data-[orientation=vertical]:h-4"
|
className="mx-2 data-[orientation=vertical]:h-4"
|
||||||
/>
|
/>
|
||||||
<div className="flex-1 max-w-sm">
|
|
||||||
<SearchTrigger onClick={() => setSearchOpen(true)} />
|
{company && (
|
||||||
</div>
|
<div className="text-muted-foreground hidden items-center gap-1.5 text-sm md:flex">
|
||||||
|
<Building2 className="size-3.5" />
|
||||||
|
<span className="max-w-[260px] truncate">{company.name}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="ml-auto flex items-center gap-2">
|
<div className="ml-auto flex items-center gap-2">
|
||||||
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
|
<div className="hidden w-full max-w-sm md:block">
|
||||||
<a
|
<SearchTrigger onClick={() => setSearchOpen(true)} />
|
||||||
href="https://shadcnstore.com/blocks"
|
</div>
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
className="dark:text-foreground"
|
|
||||||
>
|
|
||||||
Blocks
|
|
||||||
</a>
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
|
|
||||||
<a
|
|
||||||
href="/landing"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
className="dark:text-foreground"
|
|
||||||
>
|
|
||||||
Landing Page
|
|
||||||
</a>
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
|
|
||||||
<a
|
|
||||||
href="https://github.com/silicondeck/shadcn-dashboard-landing-template"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
className="dark:text-foreground"
|
|
||||||
>
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
</Button>
|
|
||||||
<ModeToggle />
|
<ModeToggle />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<CommandSearch open={searchOpen} onOpenChange={setSearchOpen} />
|
<CommandSearch open={searchOpen} onOpenChange={setSearchOpen} />
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user