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>
|
||||
);
|
||||
}
|
||||
|
||||
+119
-203
@@ -1,27 +1,24 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
import {
|
||||
LayoutPanelLeft,
|
||||
LayoutDashboard,
|
||||
Mail,
|
||||
CheckSquare,
|
||||
MessageCircle,
|
||||
Briefcase,
|
||||
Calendar,
|
||||
Shield,
|
||||
AlertTriangle,
|
||||
Settings,
|
||||
HelpCircle,
|
||||
CheckSquare,
|
||||
CreditCard,
|
||||
LayoutTemplate,
|
||||
FileText,
|
||||
LayoutDashboard,
|
||||
Package,
|
||||
Receipt,
|
||||
Settings,
|
||||
Users,
|
||||
} from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { Logo } from "@/components/logo"
|
||||
import { SidebarNotification } from "@/components/sidebar-notification"
|
||||
Wallet,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { NavMain } from "@/components/nav-main"
|
||||
import { NavUser } from "@/components/nav-user"
|
||||
import { Logo } from "@/components/logo";
|
||||
import { NavMain } from "@/components/nav-main";
|
||||
import { NavUser } from "@/components/nav-user";
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
@@ -30,186 +27,106 @@ import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar"
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
const data = {
|
||||
user: {
|
||||
name: "ShadcnStore",
|
||||
email: "store@example.com",
|
||||
avatar: "",
|
||||
import type { ShellCompany, ShellUser } from "@/app/(dashboard)/dashboard-shell";
|
||||
|
||||
const navGroups = [
|
||||
{
|
||||
label: "Genel",
|
||||
items: [
|
||||
{
|
||||
title: "Genel bakış",
|
||||
url: "/dashboard",
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
],
|
||||
},
|
||||
navGroups: [
|
||||
{
|
||||
label: "Dashboards",
|
||||
items: [
|
||||
{
|
||||
title: "Dashboard 1",
|
||||
url: "/dashboard",
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{
|
||||
title: "Dashboard 2",
|
||||
url: "/dashboard-2",
|
||||
icon: LayoutPanelLeft,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Apps",
|
||||
items: [
|
||||
{
|
||||
title: "Mail",
|
||||
url: "/mail",
|
||||
icon: Mail,
|
||||
},
|
||||
{
|
||||
title: "Tasks",
|
||||
url: "/tasks",
|
||||
icon: CheckSquare,
|
||||
},
|
||||
{
|
||||
title: "Chat",
|
||||
url: "/chat",
|
||||
icon: MessageCircle,
|
||||
},
|
||||
{
|
||||
title: "Calendar",
|
||||
url: "/calendar",
|
||||
icon: Calendar,
|
||||
},
|
||||
{
|
||||
title: "Users",
|
||||
url: "/users",
|
||||
icon: Users,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Pages",
|
||||
items: [
|
||||
{
|
||||
title: "Landing",
|
||||
url: "/landing",
|
||||
target: "_blank",
|
||||
icon: LayoutTemplate,
|
||||
},
|
||||
{
|
||||
title: "Auth Pages",
|
||||
url: "#",
|
||||
icon: Shield,
|
||||
items: [
|
||||
{
|
||||
title: "Sign In 1",
|
||||
url: "/sign-in",
|
||||
},
|
||||
{
|
||||
title: "Sign In 2",
|
||||
url: "/sign-in-2",
|
||||
},
|
||||
{
|
||||
title: "Sign In 3",
|
||||
url: "/sign-in-3",
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
{
|
||||
label: "İşletme",
|
||||
items: [
|
||||
{
|
||||
title: "Müşteriler",
|
||||
url: "/customers",
|
||||
icon: Users,
|
||||
},
|
||||
{
|
||||
title: "Hizmetler",
|
||||
url: "/services",
|
||||
icon: Briefcase,
|
||||
},
|
||||
{
|
||||
title: "Yazılımlarımız",
|
||||
url: "/software",
|
||||
icon: Package,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Operasyon",
|
||||
items: [
|
||||
{
|
||||
title: "Takvim",
|
||||
url: "/calendar",
|
||||
icon: Calendar,
|
||||
},
|
||||
{
|
||||
title: "Görevler",
|
||||
url: "/tasks",
|
||||
icon: CheckSquare,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Finans",
|
||||
items: [
|
||||
{
|
||||
title: "Gelir / Gider",
|
||||
url: "/finance",
|
||||
icon: Wallet,
|
||||
},
|
||||
{
|
||||
title: "Faturalar",
|
||||
url: "/invoices",
|
||||
icon: Receipt,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Hesap",
|
||||
items: [
|
||||
{
|
||||
title: "Çalışma alanı",
|
||||
url: "/settings/workspace",
|
||||
icon: Settings,
|
||||
items: [
|
||||
{ title: "Şirket bilgileri", url: "/settings/workspace" },
|
||||
{ title: "Ekip üyeleri", url: "/settings/members" },
|
||||
{ title: "Faturalama", url: "/settings/billing" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Profil",
|
||||
url: "/settings/account",
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
title: "Plan",
|
||||
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 (
|
||||
<Sidebar {...props}>
|
||||
<SidebarHeader>
|
||||
@@ -217,12 +134,12 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton size="lg" asChild>
|
||||
<Link href="/dashboard">
|
||||
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
|
||||
<Logo size={24} className="text-current" />
|
||||
<div className="bg-primary text-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
||||
<Logo size={20} className="text-current" />
|
||||
</div>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">ShadcnStore</span>
|
||||
<span className="truncate text-xs">Admin Dashboard</span>
|
||||
<span className="truncate font-medium">İşletmem</span>
|
||||
<span className="text-muted-foreground truncate text-xs">{company.name}</span>
|
||||
</div>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
@@ -230,14 +147,13 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
{data.navGroups.map((group) => (
|
||||
{navGroups.map((group) => (
|
||||
<NavMain key={group.label} label={group.label} items={group.items} />
|
||||
))}
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<SidebarNotification />
|
||||
<NavUser user={data.user} />
|
||||
<NavUser user={user} />
|
||||
</SidebarFooter>
|
||||
</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 {
|
||||
BellDot,
|
||||
CircleUser,
|
||||
CreditCard,
|
||||
EllipsisVertical,
|
||||
LogOut,
|
||||
BellDot,
|
||||
CircleUser,
|
||||
} from "lucide-react"
|
||||
import Link from "next/link"
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { Logo } from "@/components/logo"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -18,24 +18,33 @@ import {
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
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({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
}
|
||||
user: { name: string; email: string };
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
const { isMobile } = useSidebar();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const handleSignOut = () => {
|
||||
startTransition(async () => {
|
||||
await signOutAction();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
@@ -46,14 +55,12 @@ export function NavUser({
|
||||
size="lg"
|
||||
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">
|
||||
< Logo size={28} />
|
||||
<div className="bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg text-sm font-medium">
|
||||
{initials(user.name)}
|
||||
</div>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
<span className="text-muted-foreground truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
<EllipsisVertical className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
@@ -66,14 +73,12 @@ export function NavUser({
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<div className="h-8 w-8 rounded-lg">
|
||||
< Logo size={28} />
|
||||
<div className="bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg text-sm font-medium">
|
||||
{initials(user.name)}
|
||||
</div>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
<span className="text-muted-foreground truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
@@ -82,32 +87,34 @@ export function NavUser({
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<Link href="/settings/account">
|
||||
<CircleUser />
|
||||
Account
|
||||
Profil
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<Link href="/settings/billing">
|
||||
<CreditCard />
|
||||
Billing
|
||||
Plan & Faturalama
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<Link href="/settings/notifications">
|
||||
<BellDot />
|
||||
Notifications
|
||||
Bildirimler
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<Link href="/sign-in">
|
||||
<LogOut />
|
||||
Log out
|
||||
</Link>
|
||||
<DropdownMenuItem
|
||||
onClick={handleSignOut}
|
||||
disabled={isPending}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<LogOut />
|
||||
{isPending ? "Çıkış yapılıyor..." : "Çıkış yap"}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
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"
|
||||
import * as React from "react";
|
||||
import { Building2 } from "lucide-react";
|
||||
|
||||
export function SiteHeader() {
|
||||
const [searchOpen, setSearchOpen] = React.useState(false)
|
||||
import { CommandSearch, SearchTrigger } from "@/components/command-search";
|
||||
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(() => {
|
||||
const down = (e: KeyboardEvent) => {
|
||||
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault()
|
||||
setSearchOpen((open) => !open)
|
||||
e.preventDefault();
|
||||
setSearchOpen((open) => !open);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", down)
|
||||
return () => document.removeEventListener("keydown", down)
|
||||
}, [])
|
||||
document.addEventListener("keydown", down);
|
||||
return () => document.removeEventListener("keydown", down);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -31,45 +34,23 @@ export function SiteHeader() {
|
||||
orientation="vertical"
|
||||
className="mx-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<div className="flex-1 max-w-sm">
|
||||
<SearchTrigger onClick={() => setSearchOpen(true)} />
|
||||
</div>
|
||||
|
||||
{company && (
|
||||
<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">
|
||||
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
|
||||
<a
|
||||
href="https://shadcnstore.com/blocks"
|
||||
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>
|
||||
<div className="hidden w-full max-w-sm md:block">
|
||||
<SearchTrigger onClick={() => setSearchOpen(true)} />
|
||||
</div>
|
||||
<ModeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<CommandSearch open={searchOpen} onOpenChange={setSearchOpen} />
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user