From 5d03b2c41414a6ad6d519d189d911fcc88b8a444 Mon Sep 17 00:00:00 2001
From: Yehoshua Sandler
Date: Mon, 19 May 2025 14:05:59 -0500
Subject: [PATCH] feat: added zustand global store, LeftSidebar set to tenant +
user data
---
package-lock.json | 32 +-
package.json | 3 +-
.../[tenant]/dashboard/page.client.tsx | 22 +
.../{ => [tenant]}/dashboard/page.tsx | 11 +-
.../(frontend)/[tenant]/layout.suspense.tsx | 48 ++
src/app/(frontend)/[tenant]/layout.tsx | 60 ++
.../{[slug] => [tenant]}/page.client.tsx | 0
.../(frontend)/{[slug] => [tenant]}/page.tsx | 0
src/app/(frontend)/dashboard/layout.tsx | 16 -
src/app/(frontend)/globals.css | 51 +-
src/app/(frontend)/layout.tsx | 18 +-
src/app/(frontend)/page.client.tsx | 0
src/app/(frontend)/page.tsx | 2 +-
src/components/nav-favorites.tsx | 33 +-
src/components/nav-main.tsx | 30 +-
src/components/nav-secondary.tsx | 27 +-
src/components/nav-workspaces.tsx | 28 +-
src/components/sidebar-left.tsx | 380 ++++-------
src/components/team-switcher.tsx | 59 +-
src/components/ui/sidebar.tsx | 623 +++++++++---------
src/stores/index.ts | 23 +
tailwind.config.mjs | 204 +++---
22 files changed, 854 insertions(+), 816 deletions(-)
create mode 100644 src/app/(frontend)/[tenant]/dashboard/page.client.tsx
rename src/app/(frontend)/{ => [tenant]}/dashboard/page.tsx (82%)
create mode 100644 src/app/(frontend)/[tenant]/layout.suspense.tsx
create mode 100644 src/app/(frontend)/[tenant]/layout.tsx
rename src/app/(frontend)/{[slug] => [tenant]}/page.client.tsx (100%)
rename src/app/(frontend)/{[slug] => [tenant]}/page.tsx (100%)
delete mode 100644 src/app/(frontend)/dashboard/layout.tsx
create mode 100644 src/app/(frontend)/page.client.tsx
create mode 100644 src/stores/index.ts
diff --git a/package-lock.json b/package-lock.json
index 8246793..12c47a3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -51,7 +51,8 @@
"react-hook-form": "7.45.4",
"sharp": "0.32.6",
"tailwind-merge": "^2.3.0",
- "tailwindcss-animate": "^1.0.7"
+ "tailwindcss-animate": "^1.0.7",
+ "zustand": "^5.0.4"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
@@ -15390,6 +15391,35 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/zustand": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.4.tgz",
+ "integrity": "sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ },
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
diff --git a/package.json b/package.json
index 9125b37..6d99584 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,8 @@
"react-hook-form": "7.45.4",
"sharp": "0.32.6",
"tailwind-merge": "^2.3.0",
- "tailwindcss-animate": "^1.0.7"
+ "tailwindcss-animate": "^1.0.7",
+ "zustand": "^5.0.4"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
diff --git a/src/app/(frontend)/[tenant]/dashboard/page.client.tsx b/src/app/(frontend)/[tenant]/dashboard/page.client.tsx
new file mode 100644
index 0000000..a4968ec
--- /dev/null
+++ b/src/app/(frontend)/[tenant]/dashboard/page.client.tsx
@@ -0,0 +1,22 @@
+'use client'
+
+import { Tenant, User } from '@/payload-types'
+import useGlobal from '@/stores'
+
+type Props = {
+ user?: User
+ tenant?: Tenant
+}
+
+const DashboardPageClient = (props?: Props) => {
+ const { user, tenant } = useGlobal()
+ return (
+
+
Testing Dashboard Zustand Data Here
+
{user?.email}
+
{tenant?.name}
+
+ )
+}
+
+export default DashboardPageClient
diff --git a/src/app/(frontend)/dashboard/page.tsx b/src/app/(frontend)/[tenant]/dashboard/page.tsx
similarity index 82%
rename from src/app/(frontend)/dashboard/page.tsx
rename to src/app/(frontend)/[tenant]/dashboard/page.tsx
index 5b2eb80..eb77a25 100644
--- a/src/app/(frontend)/dashboard/page.tsx
+++ b/src/app/(frontend)/[tenant]/dashboard/page.tsx
@@ -8,6 +8,7 @@ import {
} from '@/components/ui/breadcrumb'
import { Separator } from '@/components/ui/separator'
import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'
+import DashboardPageClient from './page.client'
export default function Page() {
return (
@@ -21,18 +22,18 @@ export default function Page() {
-
- Project Management & Task Tracking
-
+ Dashboard
-
+
diff --git a/src/app/(frontend)/[tenant]/layout.suspense.tsx b/src/app/(frontend)/[tenant]/layout.suspense.tsx
new file mode 100644
index 0000000..1989acd
--- /dev/null
+++ b/src/app/(frontend)/[tenant]/layout.suspense.tsx
@@ -0,0 +1,48 @@
+'use client'
+
+import { Tenant } from '@/payload-types'
+import useGlobal from '@/stores'
+import { redirect } from 'next/navigation'
+import { AuthResult } from 'node_modules/payload/dist/auth/operations/auth'
+import { PaginatedDocs } from 'payload'
+import { ReactNode, use, useEffect } from 'react'
+
+type SuspenseProps = {
+ getUserPromise: Promise
+ getTenantPromise: Promise>
+ children: ReactNode
+}
+const RootLayoutSuspenseFrontend = (props: SuspenseProps) => {
+ const { getUserPromise, getTenantPromise, children } = props
+
+ const { user: authedUser } = use(getUserPromise)
+ const foundTenants = use(getTenantPromise)
+
+ const { setUser, setTenant } = useGlobal()
+
+ useEffect(() => {
+ try {
+ if (!authedUser || !authedUser?.id) return redirect('/login')
+
+ setUser(authedUser)
+ } catch (err) {
+ return redirect('/login')
+ }
+ }, [authedUser])
+
+ useEffect(() => {
+ try {
+ if (!foundTenants?.docs || !foundTenants.docs.length || !foundTenants.docs[0]) {
+ return redirect('/')
+ }
+
+ setTenant(foundTenants.docs[0])
+ } catch (err) {
+ return redirect('/')
+ }
+ }, [foundTenants])
+
+ return <>{children}>
+}
+
+export default RootLayoutSuspenseFrontend
diff --git a/src/app/(frontend)/[tenant]/layout.tsx b/src/app/(frontend)/[tenant]/layout.tsx
new file mode 100644
index 0000000..2ed9a19
--- /dev/null
+++ b/src/app/(frontend)/[tenant]/layout.tsx
@@ -0,0 +1,60 @@
+import configPromise from '@payload-config'
+import { getPayload, PaginatedDocs } from 'payload'
+import { headers as getHeaders } from 'next/headers.js'
+import { ReactNode, Suspense } from 'react'
+import RootLayoutSuspenseFrontend from './layout.suspense'
+import { Tenant } from '@/payload-types'
+
+export const metadata = {
+ title: 'Next.js',
+ description: 'Generated by Next.js',
+}
+
+type Props = {
+ params: Promise<{
+ tenant?: string
+ }>
+ children: ReactNode
+}
+
+const RootLayoutFrontend = async (props: Props) => {
+ const { params, children } = props
+ const { tenant: tenantSlug } = await params
+
+ const payload = await getPayload({ config: configPromise })
+ const headers = await getHeaders()
+
+ const getUserPromise = payload.auth({ headers })
+
+ const getTenantPromise = payload.find({
+ collection: 'tenants',
+ limit: 1,
+ select: {
+ name: true,
+ domain: true,
+ slug: true,
+ },
+ where: {
+ slug: {
+ equals: tenantSlug,
+ },
+ },
+ }) as Promise>
+
+ return (
+
+
+ Loading layout...
}>
+
+ {children}
+
+
+
+
+ )
+}
+
+export default RootLayoutFrontend
diff --git a/src/app/(frontend)/[slug]/page.client.tsx b/src/app/(frontend)/[tenant]/page.client.tsx
similarity index 100%
rename from src/app/(frontend)/[slug]/page.client.tsx
rename to src/app/(frontend)/[tenant]/page.client.tsx
diff --git a/src/app/(frontend)/[slug]/page.tsx b/src/app/(frontend)/[tenant]/page.tsx
similarity index 100%
rename from src/app/(frontend)/[slug]/page.tsx
rename to src/app/(frontend)/[tenant]/page.tsx
diff --git a/src/app/(frontend)/dashboard/layout.tsx b/src/app/(frontend)/dashboard/layout.tsx
deleted file mode 100644
index a14e64f..0000000
--- a/src/app/(frontend)/dashboard/layout.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-export const metadata = {
- title: 'Next.js',
- description: 'Generated by Next.js',
-}
-
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode
-}) {
- return (
-
- {children}
-
- )
-}
diff --git a/src/app/(frontend)/globals.css b/src/app/(frontend)/globals.css
index 7178694..9d69cc1 100644
--- a/src/app/(frontend)/globals.css
+++ b/src/app/(frontend)/globals.css
@@ -2,6 +2,8 @@
@tailwind components;
@tailwind utilities;
+@custom-variant dark (&:is(.dark *));
+
@layer base {
h1,
h2,
@@ -89,8 +91,8 @@
--success: 196 100% 14%;
--warning: 34 51% 25%;
--error: 10 39% 43%;
- }
- .dark {
+
+
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
@@ -100,6 +102,51 @@
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
+
+ .dark {
+ --sidebar-background: 240 5.9% 10%;
+ --sidebar-foreground: 240 4.8% 95.9%;
+ --sidebar-primary: 224.3 76.3% 48%;
+ --sidebar-primary-foreground: 0 0% 100%;
+ --sidebar-accent: 240 3.7% 15.9%;
+ --sidebar-accent-foreground: 240 4.8% 95.9%;
+ --sidebar-border: 240 3.7% 15.9%;
+ --sidebar-ring: 217.2 91.2% 59.8%;
+
+ --background: 0 0% 0%;
+ --foreground: 210 40% 98%;
+
+ --card: 0 0% 4%;
+ --card-foreground: 210 40% 98%;
+
+ --popover: 222.2 84% 4.9%;
+ --popover-foreground: 210 40% 98%;
+
+ --primary: 210 40% 98%;
+ --primary-foreground: 222.2 47.4% 11.2%;
+
+ --secondary: 217.2 32.6% 17.5%;
+ --secondary-foreground: 210 40% 98%;
+
+ --muted: 217.2 32.6% 17.5%;
+ --muted-foreground: 215 20.2% 65.1%;
+
+ --accent: 217.2 32.6% 17.5%;
+ --accent-foreground: 210 40% 98%;
+
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 210 40% 98%;
+
+ --border: 0, 0%, 15%, 0.8;
+ --input: 217.2 32.6% 17.5%;
+ --ring: 212.7 26.8% 83.9%;
+
+ --success: 196 100% 14%;
+ --warning: 34 51% 25%;
+ --error: 10 39% 43%;
+ }
+
+
}
@layer base {
diff --git a/src/app/(frontend)/layout.tsx b/src/app/(frontend)/layout.tsx
index e2e2af0..f58b836 100644
--- a/src/app/(frontend)/layout.tsx
+++ b/src/app/(frontend)/layout.tsx
@@ -2,20 +2,14 @@ import type { Metadata } from 'next'
import React from 'react'
-import { AdminBar } from '@/components/AdminBar'
-import { Footer } from '@/Footer/Component'
-import { Header } from '@/Header/Component'
import { Providers } from '@/providers'
import { InitTheme } from '@/providers/Theme/InitTheme'
import { mergeOpenGraph } from '@/utilities/mergeOpenGraph'
-import { draftMode } from 'next/headers'
import './globals.css'
import { getServerSideURL } from '@/utilities/getURL'
export default async function RootLayout({ children }: { children: React.ReactNode }) {
- const { isEnabled } = await draftMode()
-
return (
@@ -24,17 +18,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
-
-
-
-
- {children}
-
-
+ {children}
)
diff --git a/src/app/(frontend)/page.client.tsx b/src/app/(frontend)/page.client.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/(frontend)/page.tsx b/src/app/(frontend)/page.tsx
index 6aba7d6..9897086 100644
--- a/src/app/(frontend)/page.tsx
+++ b/src/app/(frontend)/page.tsx
@@ -1,4 +1,4 @@
-import PageTemplate, { generateMetadata } from './[slug]/page'
+import PageTemplate, { generateMetadata } from './[tenant]/page'
export default PageTemplate
diff --git a/src/components/nav-favorites.tsx b/src/components/nav-favorites.tsx
index 34e2aea..48428d7 100644
--- a/src/components/nav-favorites.tsx
+++ b/src/components/nav-favorites.tsx
@@ -1,12 +1,6 @@
-"use client"
+'use client'
-import {
- ArrowUpRight,
- Link,
- MoreHorizontal,
- StarOff,
- Trash2,
-} from "lucide-react"
+import { ArrowUpRight, Link, MoreHorizontal, StarOff, Trash2 } from 'lucide-react'
import {
DropdownMenu,
@@ -14,7 +8,7 @@ import {
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
+} from '@/components/ui/dropdown-menu'
import {
SidebarGroup,
SidebarGroupLabel,
@@ -23,17 +17,14 @@ import {
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
-} from "@/components/ui/sidebar"
+} from '@/components/ui/sidebar'
+import { SidebarLink } from './sidebar-left'
-export function NavFavorites({
- favorites,
-}: {
- favorites: {
- name: string
- url: string
- emoji: string
- }[]
-}) {
+type Props = {
+ favorites: SidebarLink[]
+}
+export function NavFavorites(props: Props) {
+ const { favorites } = props
const { isMobile } = useSidebar()
return (
@@ -57,8 +48,8 @@ export function NavFavorites({
diff --git a/src/components/nav-main.tsx b/src/components/nav-main.tsx
index 31edc97..b10a714 100644
--- a/src/components/nav-main.tsx
+++ b/src/components/nav-main.tsx
@@ -1,31 +1,21 @@
-"use client"
+'use client'
-import { type LucideIcon } from "lucide-react"
+import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar'
+import { SidebarLeftMainNavItem } from './sidebar-left'
-import {
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
-} from "@/components/ui/sidebar"
-
-export function NavMain({
- items,
-}: {
- items: {
- title: string
- url: string
- icon: LucideIcon
- isActive?: boolean
- }[]
-}) {
+type Props = {
+ items: SidebarLeftMainNavItem[]
+}
+export function NavMain(props: Props) {
+ const { items } = props
return (
{items.map((item) => (
-
+
- {item.title}
+ {item.name}
diff --git a/src/components/nav-secondary.tsx b/src/components/nav-secondary.tsx
index 71d2ebf..b87cf09 100644
--- a/src/components/nav-secondary.tsx
+++ b/src/components/nav-secondary.tsx
@@ -1,5 +1,4 @@
-import React from "react"
-import { type LucideIcon } from "lucide-react"
+import React from 'react'
import {
SidebarGroup,
@@ -8,29 +7,25 @@ import {
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
-} from "@/components/ui/sidebar"
+} from '@/components/ui/sidebar'
+import { SidebarLeftMainNavItem } from './sidebar-left'
-export function NavSecondary({
- items,
- ...props
-}: {
- items: {
- title: string
- url: string
- icon: LucideIcon
- badge?: React.ReactNode
- }[]
-} & React.ComponentPropsWithoutRef) {
+type Props = {
+ items: SidebarLeftMainNavItem[]
+} & React.ComponentPropsWithoutRef
+
+export function NavSecondary(props: Props) {
+ const { items } = props
return (
{items.map((item) => (
-
+
- {item.title}
+ {item.name}
{item.badge && {item.badge}}
diff --git a/src/components/nav-workspaces.tsx b/src/components/nav-workspaces.tsx
index a9627f1..fd57434 100644
--- a/src/components/nav-workspaces.tsx
+++ b/src/components/nav-workspaces.tsx
@@ -1,10 +1,6 @@
-import { ChevronRight, MoreHorizontal, Plus } from "lucide-react"
+import { ChevronRight, MoreHorizontal, Plus } from 'lucide-react'
-import {
- Collapsible,
- CollapsibleContent,
- CollapsibleTrigger,
-} from "@/components/ui/collapsible"
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
import {
SidebarGroup,
SidebarGroupContent,
@@ -16,20 +12,14 @@ import {
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
-} from "@/components/ui/sidebar"
+} from '@/components/ui/sidebar'
+import { SidebarWorkspace } from './sidebar-left'
-export function NavWorkspaces({
- workspaces,
-}: {
- workspaces: {
- name: string
- emoji: React.ReactNode
- pages: {
- name: string
- emoji: React.ReactNode
- }[]
- }[]
-}) {
+type Props = {
+ workspaces: SidebarWorkspace[]
+}
+export function NavWorkspaces(props: Props) {
+ const { workspaces } = props
return (
Workspaces
diff --git a/src/components/sidebar-left.tsx b/src/components/sidebar-left.tsx
index e5e52ce..598913c 100644
--- a/src/components/sidebar-left.tsx
+++ b/src/components/sidebar-left.tsx
@@ -1,276 +1,166 @@
-"use client"
+'use client'
-import * as React from "react"
+import * as React from 'react'
import {
- AudioWaveform,
- Blocks,
Calendar,
- Command,
Home,
Inbox,
+ LucideIcon,
MessageCircleQuestion,
Search,
Settings2,
- Sparkles,
- Trash2,
-} from "lucide-react"
+ ChefHat,
+ Dumbbell,
+ Rabbit,
+} from 'lucide-react'
-import { NavFavorites } from "@/components/nav-favorites"
-import { NavMain } from "@/components/nav-main"
-import { NavSecondary } from "@/components/nav-secondary"
-import { NavWorkspaces } from "@/components/nav-workspaces"
-import { TeamSwitcher } from "@/components/team-switcher"
-import {
- Sidebar,
- SidebarContent,
- SidebarHeader,
- SidebarRail,
-} from "@/components/ui/sidebar"
+import { NavMain } from '@/components/nav-main'
+import { NavSecondary } from '@/components/nav-secondary'
+import { TeamSwitcher } from '@/components/team-switcher'
+import { Sidebar, SidebarContent, SidebarHeader, SidebarRail } from '@/components/ui/sidebar'
+import useGlobal from '@/stores'
+import { Tenant } from '@/payload-types'
-// This is sample data.
-const data = {
- teams: [
+export type SidebarLeftTeam = {
+ id: number
+ name: string
+ logo: string
+ roles: string[]
+ slug: string
+ domain: string
+}
+
+export type SidebarLeftMainNavItem = {
+ name: string
+ url: string
+ icon: LucideIcon
+ isActive?: boolean
+ badge?: number
+}
+
+export type SidebarLink = {
+ name: string
+ url: string
+ emoji?: string
+}
+
+export type SidebarWorkspace = {
+ name: string
+ emoji?: string
+ pages: SidebarLink[]
+}
+
+const makeNavMainItems = (activeTenant: Tenant): SidebarLeftMainNavItem[] => {
+ return [
{
- name: "Acme Inc",
- logo: Command,
- plan: "Enterprise",
- },
- {
- name: "Acme Corp.",
- logo: AudioWaveform,
- plan: "Startup",
- },
- {
- name: "Evil Corp.",
- logo: Command,
- plan: "Free",
- },
- ],
- navMain: [
- {
- title: "Search",
- url: "#",
- icon: Search,
- },
- {
- title: "Ask AI",
- url: "#",
- icon: Sparkles,
- },
- {
- title: "Home",
- url: "#",
+ name: 'Home',
+ url: '#',
icon: Home,
- isActive: true,
},
{
- title: "Inbox",
- url: "#",
- icon: Inbox,
- badge: "10",
- },
- ],
- navSecondary: [
- {
- title: "Calendar",
- url: "#",
+ name: 'Calendar',
+ url: `/${activeTenant.slug}/calendar`,
icon: Calendar,
},
{
- title: "Settings",
- url: "#",
+ name: 'Workouts',
+ url: `/${activeTenant.slug}/workouts`,
+ icon: Dumbbell,
+ },
+ {
+ name: 'Nutrition',
+ url: `/${activeTenant.slug}/nutrition`,
+ icon: ChefHat,
+ },
+ {
+ name: 'Habbits',
+ url: `/${activeTenant.slug}/habbits`,
+ icon: Rabbit,
+ },
+ {
+ name: 'Inbox',
+ url: `/${activeTenant.slug}/inbox`,
+ icon: Inbox,
+ },
+ ]
+}
+
+const makeNavSecondaryItems = (activeTenant: Tenant): SidebarLeftMainNavItem[] => {
+ return [
+ {
+ name: 'Search',
+ url: `/${activeTenant.slug}/search`,
+ icon: Search,
+ },
+ {
+ name: 'Settings',
+ url: `/${activeTenant.slug}/settings`,
icon: Settings2,
},
{
- title: "Templates",
- url: "#",
- icon: Blocks,
- },
- {
- title: "Trash",
- url: "#",
- icon: Trash2,
- },
- {
- title: "Help",
- url: "#",
+ name: 'Help',
+ url: `/${activeTenant.slug}/help`,
icon: MessageCircleQuestion,
},
- ],
- favorites: [
- {
- name: "Project Management & Task Tracking",
- url: "#",
- emoji: "π",
- },
- {
- name: "Family Recipe Collection & Meal Planning",
- url: "#",
- emoji: "π³",
- },
- {
- name: "Fitness Tracker & Workout Routines",
- url: "#",
- emoji: "πͺ",
- },
- {
- name: "Book Notes & Reading List",
- url: "#",
- emoji: "π",
- },
- {
- name: "Sustainable Gardening Tips & Plant Care",
- url: "#",
- emoji: "π±",
- },
- {
- name: "Language Learning Progress & Resources",
- url: "#",
- emoji: "π£οΈ",
- },
- {
- name: "Home Renovation Ideas & Budget Tracker",
- url: "#",
- emoji: "π ",
- },
- {
- name: "Personal Finance & Investment Portfolio",
- url: "#",
- emoji: "π°",
- },
- {
- name: "Movie & TV Show Watchlist with Reviews",
- url: "#",
- emoji: "π¬",
- },
- {
- name: "Daily Habit Tracker & Goal Setting",
- url: "#",
- emoji: "β
",
- },
- ],
- workspaces: [
- {
- name: "Personal Life Management",
- emoji: "π ",
- pages: [
- {
- name: "Daily Journal & Reflection",
- url: "#",
- emoji: "π",
- },
- {
- name: "Health & Wellness Tracker",
- url: "#",
- emoji: "π",
- },
- {
- name: "Personal Growth & Learning Goals",
- url: "#",
- emoji: "π",
- },
- ],
- },
- {
- name: "Professional Development",
- emoji: "πΌ",
- pages: [
- {
- name: "Career Objectives & Milestones",
- url: "#",
- emoji: "π―",
- },
- {
- name: "Skill Acquisition & Training Log",
- url: "#",
- emoji: "π§ ",
- },
- {
- name: "Networking Contacts & Events",
- url: "#",
- emoji: "π€",
- },
- ],
- },
- {
- name: "Creative Projects",
- emoji: "π¨",
- pages: [
- {
- name: "Writing Ideas & Story Outlines",
- url: "#",
- emoji: "βοΈ",
- },
- {
- name: "Art & Design Portfolio",
- url: "#",
- emoji: "πΌοΈ",
- },
- {
- name: "Music Composition & Practice Log",
- url: "#",
- emoji: "π΅",
- },
- ],
- },
- {
- name: "Home Management",
- emoji: "π‘",
- pages: [
- {
- name: "Household Budget & Expense Tracking",
- url: "#",
- emoji: "π°",
- },
- {
- name: "Home Maintenance Schedule & Tasks",
- url: "#",
- emoji: "π§",
- },
- {
- name: "Family Calendar & Event Planning",
- url: "#",
- emoji: "π
",
- },
- ],
- },
- {
- name: "Travel & Adventure",
- emoji: "π§³",
- pages: [
- {
- name: "Trip Planning & Itineraries",
- url: "#",
- emoji: "πΊοΈ",
- },
- {
- name: "Travel Bucket List & Inspiration",
- url: "#",
- emoji: "π",
- },
- {
- name: "Travel Journal & Photo Gallery",
- url: "#",
- emoji: "πΈ",
- },
- ],
- },
- ],
+ ]
}
-export function SidebarLeft({
- ...props
-}: React.ComponentProps) {
+export type SidebarLeftProps = {
+ teams?: SidebarLeftTeam[]
+ navMain?: SidebarLeftMainNavItem[]
+ navSecondary?: SidebarLeftMainNavItem[]
+ favorites?: SidebarLink[]
+ workspaces?: SidebarWorkspace[]
+} & React.ComponentProps
+
+export function SidebarLeft(props: SidebarLeftProps) {
+ const { favorites, workspaces, ...rest } = props
+
+ const { user, tenant: activeTenant } = useGlobal()
+
+ const teams = React.useMemo(() => {
+ if (props.teams) return props.teams
+ if (!user?.tenants?.length) return [] as SidebarLeftTeam[]
+
+ const teams: SidebarLeftTeam[] = user.tenants.map((t) => {
+ const tenant = t.tenant as Tenant
+ return {
+ id: tenant.id,
+ name: tenant.name,
+ domain: tenant.domain || '',
+ slug: tenant.slug,
+ logo: '', // TODO: add logo to tenate record
+ roles: t.roles,
+ isActive: t.id === activeTenant?.id,
+ }
+ })
+ return teams
+ }, [props.teams, activeTenant, user])
+
+ const navMain = React.useMemo(() => {
+ if (props.navMain) return props.navMain
+ if (!activeTenant) return []
+
+ return makeNavMainItems(activeTenant)
+ }, [props.navMain, activeTenant])
+
+ const navSecondary = React.useMemo(() => {
+ if (props.navSecondary) return props.navSecondary
+ if (!activeTenant) return []
+
+ return makeNavSecondaryItems(activeTenant)
+ }, [props.navMain, activeTenant])
+
return (
-
+
-
-
+
+
-
-
-
+ {/**/}
+ {/**/}
+
diff --git a/src/components/team-switcher.tsx b/src/components/team-switcher.tsx
index e0ecfab..2e1f1b3 100644
--- a/src/components/team-switcher.tsx
+++ b/src/components/team-switcher.tsx
@@ -1,7 +1,7 @@
-"use client"
+'use client'
-import * as React from "react"
-import { ChevronDown, Plus } from "lucide-react"
+import * as React from 'react'
+import { ChevronDown, Plus } from 'lucide-react'
import {
DropdownMenu,
@@ -11,24 +11,29 @@ import {
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-import {
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
-} from "@/components/ui/sidebar"
+} from '@/components/ui/dropdown-menu'
+import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar'
+import { SidebarLeftTeam } from './sidebar-left'
+import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar'
-export function TeamSwitcher({
- teams,
-}: {
- teams: {
- name: string
- logo: React.ElementType
- plan: string
- }[]
-}) {
+const makeDefaultAcronym = (name: string, maxLength: number = 2) => {
+ return name
+ .split(' ')
+ .map((part) => part[0])
+ .slice(0, maxLength)
+}
+
+type Props = {
+ teams: SidebarLeftTeam[]
+}
+export function TeamSwitcher(props: Props) {
+ const { teams } = props
const [activeTeam, setActiveTeam] = React.useState(teams[0])
+ React.useEffect(() => {
+ if (!activeTeam && teams.length) setActiveTeam(teams[0])
+ }, [teams])
+
if (!activeTeam) {
return null
}
@@ -40,7 +45,12 @@ export function TeamSwitcher({
-
+
+
+
+ {makeDefaultAcronym(activeTeam.name)}
+
+
{activeTeam.name}
@@ -52,9 +62,7 @@ export function TeamSwitcher({
side="bottom"
sideOffset={4}
>
-
- Teams
-
+ Teams
{teams.map((team, index) => (
-
+
+
+
+ {makeDefaultAcronym(team.name)}
+
+
{team.name}
β{index + 1}
diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx
index 91592e6..09b2ff8 100644
--- a/src/components/ui/sidebar.tsx
+++ b/src/components/ui/sidebar.tsx
@@ -1,39 +1,34 @@
-"use client"
+'use client'
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { VariantProps, cva } from "class-variance-authority"
-import { PanelLeft } from "lucide-react"
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { VariantProps, cva } from 'class-variance-authority'
+import { PanelLeft } from 'lucide-react'
-import { useIsMobile } from "@/hooks/use-mobile"
-import { cn } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { Separator } from "@/components/ui/separator"
+import { useIsMobile } from '@/hooks/use-mobile'
+import { cn } from '@/lib/utils'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import { Separator } from '@/components/ui/separator'
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
-} from "@/components/ui/sheet"
-import { Skeleton } from "@/components/ui/skeleton"
-import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from "@/components/ui/tooltip"
+} from '@/components/ui/sheet'
+import { Skeleton } from '@/components/ui/skeleton'
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
-const SIDEBAR_COOKIE_NAME = "sidebar_state"
+const SIDEBAR_COOKIE_NAME = 'sidebar_state'
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
-const SIDEBAR_WIDTH = "16rem"
-const SIDEBAR_WIDTH_MOBILE = "18rem"
-const SIDEBAR_WIDTH_ICON = "3rem"
-const SIDEBAR_KEYBOARD_SHORTCUT = "b"
+const SIDEBAR_WIDTH = '16rem'
+const SIDEBAR_WIDTH_MOBILE = '18rem'
+const SIDEBAR_WIDTH_ICON = '3rem'
+const SIDEBAR_KEYBOARD_SHORTCUT = 'b'
type SidebarContextProps = {
- state: "expanded" | "collapsed"
+ state: 'expanded' | 'collapsed'
open: boolean
setOpen: (open: boolean) => void
openMobile: boolean
@@ -47,7 +42,7 @@ const SidebarContext = React.createContext(null)
function useSidebar() {
const context = React.useContext(SidebarContext)
if (!context) {
- throw new Error("useSidebar must be used within a SidebarProvider.")
+ throw new Error('useSidebar must be used within a SidebarProvider.')
}
return context
@@ -55,7 +50,7 @@ function useSidebar() {
const SidebarProvider = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div"> & {
+ React.ComponentProps<'div'> & {
defaultOpen?: boolean
open?: boolean
onOpenChange?: (open: boolean) => void
@@ -71,7 +66,7 @@ const SidebarProvider = React.forwardRef<
children,
...props
},
- ref
+ ref,
) => {
const isMobile = useIsMobile()
const [openMobile, setOpenMobile] = React.useState(false)
@@ -82,7 +77,7 @@ const SidebarProvider = React.forwardRef<
const open = openProp ?? _open
const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => {
- const openState = typeof value === "function" ? value(open) : value
+ const openState = typeof value === 'function' ? value(open) : value
if (setOpenProp) {
setOpenProp(openState)
} else {
@@ -92,35 +87,30 @@ const SidebarProvider = React.forwardRef<
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
},
- [setOpenProp, open]
+ [setOpenProp, open],
)
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
- return isMobile
- ? setOpenMobile((open) => !open)
- : setOpen((open) => !open)
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
}, [isMobile, setOpen, setOpenMobile])
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
- if (
- event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
- (event.metaKey || event.ctrlKey)
- ) {
+ if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
event.preventDefault()
toggleSidebar()
}
}
- window.addEventListener("keydown", handleKeyDown)
- return () => window.removeEventListener("keydown", handleKeyDown)
+ window.addEventListener('keydown', handleKeyDown)
+ return () => window.removeEventListener('keydown', handleKeyDown)
}, [toggleSidebar])
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
- const state = open ? "expanded" : "collapsed"
+ const state = open ? 'expanded' : 'collapsed'
const contextValue = React.useMemo(
() => ({
@@ -132,7 +122,7 @@ const SidebarProvider = React.forwardRef<
setOpenMobile,
toggleSidebar,
}),
- [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
)
return (
@@ -141,14 +131,14 @@ const SidebarProvider = React.forwardRef<
)
- }
+ },
)
-SidebarProvider.displayName = "SidebarProvider"
+SidebarProvider.displayName = 'SidebarProvider'
const Sidebar = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div"> & {
- side?: "left" | "right"
- variant?: "sidebar" | "floating" | "inset"
- collapsible?: "offcanvas" | "icon" | "none"
+ React.ComponentProps<'div'> & {
+ side?: 'left' | 'right'
+ variant?: 'sidebar' | 'floating' | 'inset'
+ collapsible?: 'offcanvas' | 'icon' | 'none'
}
>(
(
{
- side = "left",
- variant = "sidebar",
- collapsible = "offcanvas",
+ side = 'left',
+ variant = 'sidebar',
+ collapsible = 'offcanvas',
className,
children,
...props
},
- ref
+ ref,
) => {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
- if (collapsible === "none") {
+ if (collapsible === 'none') {
return (
{/* This is what handles the sidebar gap on desktop */}
@@ -265,9 +255,9 @@ const Sidebar = React.forwardRef<
)
- }
+ },
)
-Sidebar.displayName = "Sidebar"
+Sidebar.displayName = 'Sidebar'
const SidebarTrigger = React.forwardRef<
React.ElementRef
,
@@ -281,7 +271,7 @@ const SidebarTrigger = React.forwardRef<
data-sidebar="trigger"
variant="ghost"
size="icon"
- className={cn("h-7 w-7", className)}
+ className={cn('h-7 w-7', className)}
onClick={(event) => {
onClick?.(event)
toggleSidebar()
@@ -293,54 +283,52 @@ const SidebarTrigger = React.forwardRef<
)
})
-SidebarTrigger.displayName = "SidebarTrigger"
+SidebarTrigger.displayName = 'SidebarTrigger'
-const SidebarRail = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps<"button">
->(({ className, ...props }, ref) => {
- const { toggleSidebar } = useSidebar()
+const SidebarRail = React.forwardRef>(
+ ({ className, ...props }, ref) => {
+ const { toggleSidebar } = useSidebar()
- return (
-
- )
-})
-SidebarRail.displayName = "SidebarRail"
+ return (
+
+ )
+ },
+)
+SidebarRail.displayName = 'SidebarRail'
-const SidebarInset = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"main">
->(({ className, ...props }, ref) => {
- return (
-
- )
-})
-SidebarInset.displayName = "SidebarInset"
+const SidebarInset = React.forwardRef>(
+ ({ className, ...props }, ref) => {
+ return (
+
+ )
+ },
+)
+SidebarInset.displayName = 'SidebarInset'
const SidebarInput = React.forwardRef<
React.ElementRef,
@@ -351,44 +339,42 @@ const SidebarInput = React.forwardRef<
ref={ref}
data-sidebar="input"
className={cn(
- "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
- className
+ 'h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring',
+ className,
)}
{...props}
/>
)
})
-SidebarInput.displayName = "SidebarInput"
+SidebarInput.displayName = 'SidebarInput'
-const SidebarHeader = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div">
->(({ className, ...props }, ref) => {
- return (
-
- )
-})
-SidebarHeader.displayName = "SidebarHeader"
+const SidebarHeader = React.forwardRef>(
+ ({ className, ...props }, ref) => {
+ return (
+
+ )
+ },
+)
+SidebarHeader.displayName = 'SidebarHeader'
-const SidebarFooter = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div">
->(({ className, ...props }, ref) => {
- return (
-
- )
-})
-SidebarFooter.displayName = "SidebarFooter"
+const SidebarFooter = React.forwardRef>(
+ ({ className, ...props }, ref) => {
+ return (
+
+ )
+ },
+)
+SidebarFooter.displayName = 'SidebarFooter'
const SidebarSeparator = React.forwardRef<
React.ElementRef,
@@ -398,154 +384,149 @@ const SidebarSeparator = React.forwardRef<
)
})
-SidebarSeparator.displayName = "SidebarSeparator"
+SidebarSeparator.displayName = 'SidebarSeparator'
-const SidebarContent = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div">
->(({ className, ...props }, ref) => {
- return (
-
- )
-})
-SidebarContent.displayName = "SidebarContent"
+const SidebarContent = React.forwardRef>(
+ ({ className, ...props }, ref) => {
+ return (
+
+ )
+ },
+)
+SidebarContent.displayName = 'SidebarContent'
-const SidebarGroup = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div">
->(({ className, ...props }, ref) => {
- return (
-
- )
-})
-SidebarGroup.displayName = "SidebarGroup"
+const SidebarGroup = React.forwardRef>(
+ ({ className, ...props }, ref) => {
+ return (
+
+ )
+ },
+)
+SidebarGroup.displayName = 'SidebarGroup'
const SidebarGroupLabel = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div"> & { asChild?: boolean }
+ React.ComponentProps<'div'> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "div"
+ const Comp = asChild ? Slot : 'div'
return (
svg]:size-4 [&>svg]:shrink-0",
- "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
- className
+ 'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
+ 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
+ className,
)}
{...props}
/>
)
})
-SidebarGroupLabel.displayName = "SidebarGroupLabel"
+SidebarGroupLabel.displayName = 'SidebarGroupLabel'
const SidebarGroupAction = React.forwardRef<
HTMLButtonElement,
- React.ComponentProps<"button"> & { asChild?: boolean }
+ React.ComponentProps<'button'> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button"
+ const Comp = asChild ? Slot : 'button'
return (
svg]:size-4 [&>svg]:shrink-0",
+ 'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
- "after:absolute after:-inset-2 after:md:hidden",
- "group-data-[collapsible=icon]:hidden",
- className
+ 'after:absolute after:-inset-2 after:md:hidden',
+ 'group-data-[collapsible=icon]:hidden',
+ className,
)}
{...props}
/>
)
})
-SidebarGroupAction.displayName = "SidebarGroupAction"
+SidebarGroupAction.displayName = 'SidebarGroupAction'
-const SidebarGroupContent = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div">
->(({ className, ...props }, ref) => (
-
-))
-SidebarGroupContent.displayName = "SidebarGroupContent"
+const SidebarGroupContent = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ ),
+)
+SidebarGroupContent.displayName = 'SidebarGroupContent'
-const SidebarMenu = React.forwardRef<
- HTMLUListElement,
- React.ComponentProps<"ul">
->(({ className, ...props }, ref) => (
-
-))
-SidebarMenu.displayName = "SidebarMenu"
+const SidebarMenu = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ ),
+)
+SidebarMenu.displayName = 'SidebarMenu'
-const SidebarMenuItem = React.forwardRef<
- HTMLLIElement,
- React.ComponentProps<"li">
->(({ className, ...props }, ref) => (
-
-))
-SidebarMenuItem.displayName = "SidebarMenuItem"
+const SidebarMenuItem = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ ),
+)
+SidebarMenuItem.displayName = 'SidebarMenuItem'
const sidebarMenuButtonVariants = cva(
- "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+ 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
{
variants: {
variant: {
- default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
+ default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
outline:
- "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
+ 'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
},
size: {
- default: "h-8 text-sm",
- sm: "h-7 text-xs",
- lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
+ default: 'h-8 text-sm',
+ sm: 'h-7 text-xs',
+ lg: 'h-12 text-sm group-data-[collapsible=icon]:!p-0',
},
},
defaultVariants: {
- variant: "default",
- size: "default",
+ variant: 'default',
+ size: 'default',
},
- }
+ },
)
const SidebarMenuButton = React.forwardRef<
HTMLButtonElement,
- React.ComponentProps<"button"> & {
+ React.ComponentProps<'button'> & {
asChild?: boolean
isActive?: boolean
tooltip?: string | React.ComponentProps
@@ -555,15 +536,15 @@ const SidebarMenuButton = React.forwardRef<
{
asChild = false,
isActive = false,
- variant = "default",
- size = "default",
+ variant = 'default',
+ size = 'default',
tooltip,
className,
...props
},
- ref
+ ref,
) => {
- const Comp = asChild ? Slot : "button"
+ const Comp = asChild ? Slot : 'button'
const { isMobile, state } = useSidebar()
const button = (
@@ -581,7 +562,7 @@ const SidebarMenuButton = React.forwardRef<
return button
}
- if (typeof tooltip === "string") {
+ if (typeof tooltip === 'string') {
tooltip = {
children: tooltip,
}
@@ -593,70 +574,69 @@ const SidebarMenuButton = React.forwardRef<
)
- }
+ },
)
-SidebarMenuButton.displayName = "SidebarMenuButton"
+SidebarMenuButton.displayName = 'SidebarMenuButton'
const SidebarMenuAction = React.forwardRef<
HTMLButtonElement,
- React.ComponentProps<"button"> & {
+ React.ComponentProps<'button'> & {
asChild?: boolean
showOnHover?: boolean
}
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button"
+ const Comp = asChild ? Slot : 'button'
return (
svg]:size-4 [&>svg]:shrink-0",
+ 'absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
- "after:absolute after:-inset-2 after:md:hidden",
- "peer-data-[size=sm]/menu-button:top-1",
- "peer-data-[size=default]/menu-button:top-1.5",
- "peer-data-[size=lg]/menu-button:top-2.5",
- "group-data-[collapsible=icon]:hidden",
+ 'after:absolute after:-inset-2 after:md:hidden',
+ 'peer-data-[size=sm]/menu-button:top-1',
+ 'peer-data-[size=default]/menu-button:top-1.5',
+ 'peer-data-[size=lg]/menu-button:top-2.5',
+ 'group-data-[collapsible=icon]:hidden',
showOnHover &&
- "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
- className
+ 'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
+ className,
)}
{...props}
/>
)
})
-SidebarMenuAction.displayName = "SidebarMenuAction"
+SidebarMenuAction.displayName = 'SidebarMenuAction'
-const SidebarMenuBadge = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div">
->(({ className, ...props }, ref) => (
-
-))
-SidebarMenuBadge.displayName = "SidebarMenuBadge"
+const SidebarMenuBadge = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ ),
+)
+SidebarMenuBadge.displayName = 'SidebarMenuBadge'
const SidebarMenuSkeleton = React.forwardRef<
HTMLDivElement,
- React.ComponentProps<"div"> & {
+ React.ComponentProps<'div'> & {
showIcon?: boolean
}
>(({ className, showIcon = false, ...props }, ref) => {
@@ -669,61 +649,54 @@ const SidebarMenuSkeleton = React.forwardRef<
- {showIcon && (
-
- )}
+ {showIcon && }
)
})
-SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"
+SidebarMenuSkeleton.displayName = 'SidebarMenuSkeleton'
-const SidebarMenuSub = React.forwardRef<
- HTMLUListElement,
- React.ComponentProps<"ul">
->(({ className, ...props }, ref) => (
-
-))
-SidebarMenuSub.displayName = "SidebarMenuSub"
+const SidebarMenuSub = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ ),
+)
+SidebarMenuSub.displayName = 'SidebarMenuSub'
-const SidebarMenuSubItem = React.forwardRef<
- HTMLLIElement,
- React.ComponentProps<"li">
->(({ ...props }, ref) => )
-SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
+const SidebarMenuSubItem = React.forwardRef>(
+ ({ ...props }, ref) => ,
+)
+SidebarMenuSubItem.displayName = 'SidebarMenuSubItem'
const SidebarMenuSubButton = React.forwardRef<
HTMLAnchorElement,
- React.ComponentProps<"a"> & {
+ React.ComponentProps<'a'> & {
asChild?: boolean
- size?: "sm" | "md"
+ size?: 'sm' | 'md'
isActive?: boolean
}
->(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
- const Comp = asChild ? Slot : "a"
+>(({ asChild = false, size = 'md', isActive, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : 'a'
return (
span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
- "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
- size === "sm" && "text-xs",
- size === "md" && "text-sm",
- "group-data-[collapsible=icon]:hidden",
- className
+ 'flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
+ 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
+ size === 'sm' && 'text-xs',
+ size === 'md' && 'text-sm',
+ 'group-data-[collapsible=icon]:hidden',
+ className,
)}
{...props}
/>
)
})
-SidebarMenuSubButton.displayName = "SidebarMenuSubButton"
+SidebarMenuSubButton.displayName = 'SidebarMenuSubButton'
export {
Sidebar,
diff --git a/src/stores/index.ts b/src/stores/index.ts
new file mode 100644
index 0000000..8f0de43
--- /dev/null
+++ b/src/stores/index.ts
@@ -0,0 +1,23 @@
+import { create } from 'zustand'
+import { User, Tenant } from '@/payload-types'
+
+export type GlobalProps = {
+ user?: User,
+ tenant?: Tenant,
+}
+
+export type GlobalMethods = {
+ setUser: (user: User) => void,
+ setTenant: (tenant: Tenant) => void,
+}
+
+export type GlobalStore = GlobalProps & GlobalMethods
+
+const useGlobal = create((set, get) => ({
+ user: undefined,
+ setUser: (user: User) => set(() => ({ user: user })),
+ setTenant: (tenant: Tenant) => set(() => ({ tenant: tenant })),
+ tenant: undefined,
+}))
+
+export default useGlobal
diff --git a/tailwind.config.mjs b/tailwind.config.mjs
index 3bf3331..693eb18 100644
--- a/tailwind.config.mjs
+++ b/tailwind.config.mjs
@@ -9,7 +9,7 @@ const config = {
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
- darkMode: ['selector', '[data-theme="dark"]', 'class'],
+ darkMode: ['selector', '[data-theme="dark"]', 'class', '.dark'],
plugins: [tailwindcssAnimate, typography],
prefix: '',
safelist: [
@@ -27,109 +27,105 @@ const config = {
'bg-warning/30',
],
theme: {
- container: {
- center: true,
- padding: {
- '2xl': '2rem',
- DEFAULT: '1rem',
- lg: '2rem',
- md: '2rem',
- sm: '1rem',
- xl: '2rem'
- },
- screens: {
- '2xl': '86rem',
- lg: '64rem',
- md: '48rem',
- sm: '40rem',
- xl: '80rem'
- }
- },
- extend: {
- animation: {
- 'accordion-down': 'accordion-down 0.2s ease-out',
- 'accordion-up': 'accordion-up 0.2s ease-out'
- },
- borderRadius: {
- lg: 'var(--radius)',
- md: 'calc(var(--radius) - 2px)',
- sm: 'calc(var(--radius) - 4px)'
- },
- colors: {
- accent: {
- DEFAULT: 'hsl(var(--accent))',
- foreground: 'hsl(var(--accent-foreground))'
- },
- background: 'hsl(var(--background))',
- border: 'hsla(var(--border))',
- card: {
- DEFAULT: 'hsl(var(--card))',
- foreground: 'hsl(var(--card-foreground))'
- },
- destructive: {
- DEFAULT: 'hsl(var(--destructive))',
- foreground: 'hsl(var(--destructive-foreground))'
- },
- foreground: 'hsl(var(--foreground))',
- input: 'hsl(var(--input))',
- muted: {
- DEFAULT: 'hsl(var(--muted))',
- foreground: 'hsl(var(--muted-foreground))'
- },
- popover: {
- DEFAULT: 'hsl(var(--popover))',
- foreground: 'hsl(var(--popover-foreground))'
- },
- primary: {
- DEFAULT: 'hsl(var(--primary))',
- foreground: 'hsl(var(--primary-foreground))'
- },
- ring: 'hsl(var(--ring))',
- secondary: {
- DEFAULT: 'hsl(var(--secondary))',
- foreground: 'hsl(var(--secondary-foreground))'
- },
- success: 'hsl(var(--success))',
- error: 'hsl(var(--error))',
- warning: 'hsl(var(--warning))',
- sidebar: {
- DEFAULT: 'hsl(var(--sidebar-background))',
- foreground: 'hsl(var(--sidebar-foreground))',
- primary: 'hsl(var(--sidebar-primary))',
- 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
- accent: 'hsl(var(--sidebar-accent))',
- 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
- border: 'hsl(var(--sidebar-border))',
- ring: 'hsl(var(--sidebar-ring))'
- }
- },
- fontFamily: {
- mono: [
- 'var(--font-geist-mono)'
- ],
- sans: [
- 'var(--font-geist-sans)'
- ]
- },
- keyframes: {
- 'accordion-down': {
- from: {
- height: '0'
- },
- to: {
- height: 'var(--radix-accordion-content-height)'
- }
- },
- 'accordion-up': {
- from: {
- height: 'var(--radix-accordion-content-height)'
- },
- to: {
- height: '0'
- }
- }
- }
- }
+ container: {
+ center: true,
+ padding: {
+ '2xl': '2rem',
+ DEFAULT: '1rem',
+ lg: '2rem',
+ md: '2rem',
+ sm: '1rem',
+ xl: '2rem',
+ },
+ screens: {
+ '2xl': '86rem',
+ lg: '64rem',
+ md: '48rem',
+ sm: '40rem',
+ xl: '80rem',
+ },
+ },
+ extend: {
+ animation: {
+ 'accordion-down': 'accordion-down 0.2s ease-out',
+ 'accordion-up': 'accordion-up 0.2s ease-out',
+ },
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)',
+ },
+ colors: {
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))',
+ },
+ background: 'hsl(var(--background))',
+ border: 'hsla(var(--border))',
+ card: {
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))',
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))',
+ },
+ foreground: 'hsl(var(--foreground))',
+ input: 'hsl(var(--input))',
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))',
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover))',
+ foreground: 'hsl(var(--popover-foreground))',
+ },
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))',
+ },
+ ring: 'hsl(var(--ring))',
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))',
+ },
+ success: 'hsl(var(--success))',
+ error: 'hsl(var(--error))',
+ warning: 'hsl(var(--warning))',
+ sidebar: {
+ DEFAULT: 'hsl(var(--sidebar-background))',
+ foreground: 'hsl(var(--sidebar-foreground))',
+ primary: 'hsl(var(--sidebar-primary))',
+ 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
+ accent: 'hsl(var(--sidebar-accent))',
+ 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
+ border: 'hsl(var(--sidebar-border))',
+ ring: 'hsl(var(--sidebar-ring))',
+ },
+ },
+ fontFamily: {
+ mono: ['var(--font-geist-mono)'],
+ sans: ['var(--font-geist-sans)'],
+ },
+ keyframes: {
+ 'accordion-down': {
+ from: {
+ height: '0',
+ },
+ to: {
+ height: 'var(--radix-accordion-content-height)',
+ },
+ },
+ 'accordion-up': {
+ from: {
+ height: 'var(--radix-accordion-content-height)',
+ },
+ to: {
+ height: '0',
+ },
+ },
+ },
+ },
},
}