@victusvinceere/saas-admin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/index.d.mts +59 -0
- package/dist/components/index.d.ts +59 -0
- package/dist/components/index.js +281 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +252 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +281 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +252 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +52 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/admin-sidebar.tsx
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
import { usePathname } from "next/navigation";
|
|
7
|
+
import {
|
|
8
|
+
LayoutDashboard,
|
|
9
|
+
Users,
|
|
10
|
+
BarChart3,
|
|
11
|
+
CreditCard,
|
|
12
|
+
Settings,
|
|
13
|
+
ArrowLeft,
|
|
14
|
+
ChevronsLeft,
|
|
15
|
+
ChevronsRight,
|
|
16
|
+
Shield
|
|
17
|
+
} from "lucide-react";
|
|
18
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
19
|
+
var defaultNavItems = [
|
|
20
|
+
{
|
|
21
|
+
title: "Overview",
|
|
22
|
+
url: "/admin",
|
|
23
|
+
icon: LayoutDashboard
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
title: "Users",
|
|
27
|
+
url: "/admin/users",
|
|
28
|
+
icon: Users
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
title: "Analytics",
|
|
32
|
+
url: "/admin/analytics",
|
|
33
|
+
icon: BarChart3
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
title: "Subscriptions",
|
|
37
|
+
url: "/admin/subscriptions",
|
|
38
|
+
icon: CreditCard
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
title: "Settings",
|
|
42
|
+
url: "/admin/settings",
|
|
43
|
+
icon: Settings
|
|
44
|
+
}
|
|
45
|
+
];
|
|
46
|
+
function AdminSidebar({
|
|
47
|
+
user,
|
|
48
|
+
navItems = defaultNavItems,
|
|
49
|
+
dashboardUrl = "/dashboard",
|
|
50
|
+
className
|
|
51
|
+
}) {
|
|
52
|
+
const pathname = usePathname();
|
|
53
|
+
const [isCollapsed, setIsCollapsed] = React.useState(false);
|
|
54
|
+
const isActive = (url) => {
|
|
55
|
+
if (url === "/admin") {
|
|
56
|
+
return pathname === url;
|
|
57
|
+
}
|
|
58
|
+
return pathname === url || pathname.startsWith(url);
|
|
59
|
+
};
|
|
60
|
+
return /* @__PURE__ */ jsx(
|
|
61
|
+
"aside",
|
|
62
|
+
{
|
|
63
|
+
className: `hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:flex-col transition-all duration-300 ${isCollapsed ? "lg:w-16" : "lg:w-64"} ${className}`,
|
|
64
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex grow flex-col gap-y-5 overflow-y-auto border-r border-red-500/20 bg-red-500/5 pb-4", children: [
|
|
65
|
+
/* @__PURE__ */ jsx("div", { className: `flex h-16 shrink-0 items-center border-b border-red-500/20 px-4 ${isCollapsed ? "justify-center px-2" : ""}`, children: /* @__PURE__ */ jsxs(Link, { href: "/admin", className: "flex items-center gap-2", children: [
|
|
66
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-red-500 text-white", children: /* @__PURE__ */ jsx(Shield, { className: "h-5 w-5" }) }),
|
|
67
|
+
!isCollapsed && /* @__PURE__ */ jsx("span", { className: "text-lg font-semibold", children: "Admin Panel" })
|
|
68
|
+
] }) }),
|
|
69
|
+
/* @__PURE__ */ jsx("nav", { className: "flex flex-1 flex-col px-2", children: /* @__PURE__ */ jsxs("ul", { className: "flex flex-1 flex-col gap-y-1", children: [
|
|
70
|
+
!isCollapsed && /* @__PURE__ */ jsx("li", { className: "mb-2 px-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: "Management" }),
|
|
71
|
+
navItems.map((item) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
72
|
+
Link,
|
|
73
|
+
{
|
|
74
|
+
href: item.url,
|
|
75
|
+
className: `group flex items-center gap-x-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors ${isActive(item.url) ? "bg-red-500 text-white" : "text-muted-foreground hover:bg-red-500/10 hover:text-red-600"} ${isCollapsed ? "justify-center px-2" : ""}`,
|
|
76
|
+
children: [
|
|
77
|
+
/* @__PURE__ */ jsx(item.icon, { className: "h-5 w-5 shrink-0" }),
|
|
78
|
+
!isCollapsed && /* @__PURE__ */ jsx("span", { children: item.title })
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
) }, item.title)),
|
|
82
|
+
/* @__PURE__ */ jsx("li", { className: "mt-auto", children: /* @__PURE__ */ jsxs(
|
|
83
|
+
Link,
|
|
84
|
+
{
|
|
85
|
+
href: dashboardUrl,
|
|
86
|
+
className: `group flex items-center gap-x-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors text-muted-foreground hover:bg-muted hover:text-foreground ${isCollapsed ? "justify-center px-2" : ""}`,
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ jsx(ArrowLeft, { className: "h-5 w-5 shrink-0" }),
|
|
89
|
+
!isCollapsed && /* @__PURE__ */ jsx("span", { children: "Back to Dashboard" })
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
) })
|
|
93
|
+
] }) }),
|
|
94
|
+
/* @__PURE__ */ jsxs("div", { className: "border-t border-red-500/20 px-2 pt-4", children: [
|
|
95
|
+
/* @__PURE__ */ jsxs("div", { className: `flex items-center gap-3 rounded-lg px-3 py-2 ${isCollapsed ? "justify-center px-2" : ""}`, children: [
|
|
96
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-red-500/10 text-red-600", children: user.name?.[0]?.toUpperCase() || "A" }),
|
|
97
|
+
!isCollapsed && /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-hidden", children: [
|
|
98
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: user.name }),
|
|
99
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: user.role.replace("_", " ") })
|
|
100
|
+
] })
|
|
101
|
+
] }),
|
|
102
|
+
/* @__PURE__ */ jsx(
|
|
103
|
+
"button",
|
|
104
|
+
{
|
|
105
|
+
onClick: () => setIsCollapsed(!isCollapsed),
|
|
106
|
+
className: `mt-2 flex w-full items-center gap-x-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors text-muted-foreground hover:bg-muted hover:text-foreground ${isCollapsed ? "justify-center px-2" : ""}`,
|
|
107
|
+
children: isCollapsed ? /* @__PURE__ */ jsx(ChevronsRight, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
108
|
+
/* @__PURE__ */ jsx(ChevronsLeft, { className: "h-4 w-4" }),
|
|
109
|
+
/* @__PURE__ */ jsx("span", { children: "Collapse" })
|
|
110
|
+
] })
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
] })
|
|
114
|
+
] })
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/components/admin-header.tsx
|
|
120
|
+
import * as React2 from "react";
|
|
121
|
+
import Link2 from "next/link";
|
|
122
|
+
import { usePathname as usePathname2 } from "next/navigation";
|
|
123
|
+
import { Menu, Shield as Shield2, ChevronRight } from "lucide-react";
|
|
124
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
125
|
+
var defaultRouteLabels = {
|
|
126
|
+
admin: "Admin",
|
|
127
|
+
users: "Users",
|
|
128
|
+
analytics: "Analytics",
|
|
129
|
+
subscriptions: "Subscriptions",
|
|
130
|
+
settings: "Settings"
|
|
131
|
+
};
|
|
132
|
+
function AdminHeader({
|
|
133
|
+
onMenuClick,
|
|
134
|
+
routeLabels = defaultRouteLabels,
|
|
135
|
+
children
|
|
136
|
+
}) {
|
|
137
|
+
const pathname = usePathname2();
|
|
138
|
+
const getBreadcrumbs = () => {
|
|
139
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
140
|
+
const breadcrumbs2 = [];
|
|
141
|
+
let currentPath = "";
|
|
142
|
+
segments.forEach((segment, index) => {
|
|
143
|
+
currentPath += `/${segment}`;
|
|
144
|
+
const label = routeLabels[segment] || segment.charAt(0).toUpperCase() + segment.slice(1);
|
|
145
|
+
breadcrumbs2.push({
|
|
146
|
+
label,
|
|
147
|
+
href: currentPath,
|
|
148
|
+
isLast: index === segments.length - 1
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
return breadcrumbs2;
|
|
152
|
+
};
|
|
153
|
+
const breadcrumbs = getBreadcrumbs();
|
|
154
|
+
return /* @__PURE__ */ jsx2("header", { className: "flex h-16 shrink-0 items-center gap-2 border-b border-red-500/20 bg-red-500/5", children: /* @__PURE__ */ jsxs2("div", { className: "flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6", children: [
|
|
155
|
+
onMenuClick && /* @__PURE__ */ jsxs2(
|
|
156
|
+
"button",
|
|
157
|
+
{
|
|
158
|
+
onClick: onMenuClick,
|
|
159
|
+
className: "inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-muted hover:text-foreground md:hidden",
|
|
160
|
+
children: [
|
|
161
|
+
/* @__PURE__ */ jsx2(Menu, { className: "h-5 w-5" }),
|
|
162
|
+
/* @__PURE__ */ jsx2("span", { className: "sr-only", children: "Toggle menu" })
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
),
|
|
166
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 rounded-md bg-red-500/10 px-2 py-1 text-xs font-medium text-red-600 dark:text-red-400 mr-2", children: [
|
|
167
|
+
/* @__PURE__ */ jsx2(Shield2, { className: "h-3 w-3" }),
|
|
168
|
+
"ADMIN"
|
|
169
|
+
] }),
|
|
170
|
+
/* @__PURE__ */ jsx2("nav", { className: "flex items-center gap-1 text-sm", children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ jsxs2(React2.Fragment, { children: [
|
|
171
|
+
index > 0 && /* @__PURE__ */ jsx2(ChevronRight, { className: "h-4 w-4 text-muted-foreground" }),
|
|
172
|
+
crumb.isLast ? /* @__PURE__ */ jsx2("span", { className: "font-medium text-foreground", children: crumb.label }) : /* @__PURE__ */ jsx2(
|
|
173
|
+
Link2,
|
|
174
|
+
{
|
|
175
|
+
href: crumb.href,
|
|
176
|
+
className: "text-muted-foreground hover:text-foreground",
|
|
177
|
+
children: crumb.label
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
] }, crumb.href)) }),
|
|
181
|
+
/* @__PURE__ */ jsx2("div", { className: "flex-1" }),
|
|
182
|
+
children
|
|
183
|
+
] }) });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/components/admin-layout.tsx
|
|
187
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
188
|
+
function AdminLayout({
|
|
189
|
+
children,
|
|
190
|
+
user,
|
|
191
|
+
navItems,
|
|
192
|
+
dashboardUrl,
|
|
193
|
+
headerContent,
|
|
194
|
+
routeLabels
|
|
195
|
+
}) {
|
|
196
|
+
return /* @__PURE__ */ jsxs3("div", { className: "min-h-screen bg-background", children: [
|
|
197
|
+
/* @__PURE__ */ jsx3(
|
|
198
|
+
AdminSidebar,
|
|
199
|
+
{
|
|
200
|
+
user,
|
|
201
|
+
navItems,
|
|
202
|
+
dashboardUrl
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
/* @__PURE__ */ jsxs3("div", { className: "lg:pl-64", children: [
|
|
206
|
+
/* @__PURE__ */ jsx3(AdminHeader, { routeLabels, children: headerContent }),
|
|
207
|
+
/* @__PURE__ */ jsx3("main", { className: "p-4 sm:p-6 lg:p-8", children })
|
|
208
|
+
] })
|
|
209
|
+
] });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/components/stats-card.tsx
|
|
213
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
214
|
+
function StatsCard({
|
|
215
|
+
title,
|
|
216
|
+
value,
|
|
217
|
+
description,
|
|
218
|
+
icon: Icon,
|
|
219
|
+
trend,
|
|
220
|
+
className
|
|
221
|
+
}) {
|
|
222
|
+
return /* @__PURE__ */ jsxs4("div", { className: `rounded-lg border bg-card p-6 ${className}`, children: [
|
|
223
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between", children: [
|
|
224
|
+
/* @__PURE__ */ jsx4("p", { className: "text-sm font-medium text-muted-foreground", children: title }),
|
|
225
|
+
Icon && /* @__PURE__ */ jsx4("div", { className: "rounded-md bg-primary/10 p-2", children: /* @__PURE__ */ jsx4(Icon, { className: "h-4 w-4 text-primary" }) })
|
|
226
|
+
] }),
|
|
227
|
+
/* @__PURE__ */ jsxs4("div", { className: "mt-2", children: [
|
|
228
|
+
/* @__PURE__ */ jsx4("p", { className: "text-2xl font-bold", children: value }),
|
|
229
|
+
(description || trend) && /* @__PURE__ */ jsxs4("div", { className: "mt-1 flex items-center gap-2 text-sm", children: [
|
|
230
|
+
trend && /* @__PURE__ */ jsxs4(
|
|
231
|
+
"span",
|
|
232
|
+
{
|
|
233
|
+
className: trend.isPositive ? "text-green-600" : "text-red-600",
|
|
234
|
+
children: [
|
|
235
|
+
trend.isPositive ? "+" : "-",
|
|
236
|
+
Math.abs(trend.value),
|
|
237
|
+
"%"
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
),
|
|
241
|
+
description && /* @__PURE__ */ jsx4("span", { className: "text-muted-foreground", children: description })
|
|
242
|
+
] })
|
|
243
|
+
] })
|
|
244
|
+
] });
|
|
245
|
+
}
|
|
246
|
+
export {
|
|
247
|
+
AdminHeader,
|
|
248
|
+
AdminLayout,
|
|
249
|
+
AdminSidebar,
|
|
250
|
+
StatsCard
|
|
251
|
+
};
|
|
252
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/admin-sidebar.tsx","../src/components/admin-header.tsx","../src/components/admin-layout.tsx","../src/components/stats-card.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport Link from \"next/link\";\nimport { usePathname } from \"next/navigation\";\nimport {\n LayoutDashboard,\n Users,\n BarChart3,\n CreditCard,\n Settings,\n ArrowLeft,\n ChevronsLeft,\n ChevronsRight,\n Shield,\n type LucideIcon,\n} from \"lucide-react\";\n\nexport interface AdminNavItem {\n title: string;\n url: string;\n icon: LucideIcon;\n}\n\nexport interface AdminSidebarProps {\n user: {\n name: string;\n email: string;\n avatar?: string;\n role: string;\n };\n navItems?: AdminNavItem[];\n dashboardUrl?: string;\n className?: string;\n}\n\nconst defaultNavItems: AdminNavItem[] = [\n {\n title: \"Overview\",\n url: \"/admin\",\n icon: LayoutDashboard,\n },\n {\n title: \"Users\",\n url: \"/admin/users\",\n icon: Users,\n },\n {\n title: \"Analytics\",\n url: \"/admin/analytics\",\n icon: BarChart3,\n },\n {\n title: \"Subscriptions\",\n url: \"/admin/subscriptions\",\n icon: CreditCard,\n },\n {\n title: \"Settings\",\n url: \"/admin/settings\",\n icon: Settings,\n },\n];\n\nexport function AdminSidebar({\n user,\n navItems = defaultNavItems,\n dashboardUrl = \"/dashboard\",\n className,\n}: AdminSidebarProps) {\n const pathname = usePathname();\n const [isCollapsed, setIsCollapsed] = React.useState(false);\n\n const isActive = (url: string) => {\n if (url === \"/admin\") {\n return pathname === url;\n }\n return pathname === url || pathname.startsWith(url);\n };\n\n return (\n <aside\n className={`hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:flex-col transition-all duration-300 ${\n isCollapsed ? \"lg:w-16\" : \"lg:w-64\"\n } ${className}`}\n >\n <div className=\"flex grow flex-col gap-y-5 overflow-y-auto border-r border-red-500/20 bg-red-500/5 pb-4\">\n {/* Header */}\n <div className={`flex h-16 shrink-0 items-center border-b border-red-500/20 px-4 ${isCollapsed ? \"justify-center px-2\" : \"\"}`}>\n <Link href=\"/admin\" className=\"flex items-center gap-2\">\n <div className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-red-500 text-white\">\n <Shield className=\"h-5 w-5\" />\n </div>\n {!isCollapsed && (\n <span className=\"text-lg font-semibold\">Admin Panel</span>\n )}\n </Link>\n </div>\n\n {/* Navigation */}\n <nav className=\"flex flex-1 flex-col px-2\">\n <ul className=\"flex flex-1 flex-col gap-y-1\">\n {!isCollapsed && (\n <li className=\"mb-2 px-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n Management\n </li>\n )}\n {navItems.map((item) => (\n <li key={item.title}>\n <Link\n href={item.url}\n className={`group flex items-center gap-x-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors ${\n isActive(item.url)\n ? \"bg-red-500 text-white\"\n : \"text-muted-foreground hover:bg-red-500/10 hover:text-red-600\"\n } ${isCollapsed ? \"justify-center px-2\" : \"\"}`}\n >\n <item.icon className=\"h-5 w-5 shrink-0\" />\n {!isCollapsed && <span>{item.title}</span>}\n </Link>\n </li>\n ))}\n\n <li className=\"mt-auto\">\n <Link\n href={dashboardUrl}\n className={`group flex items-center gap-x-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors text-muted-foreground hover:bg-muted hover:text-foreground ${\n isCollapsed ? \"justify-center px-2\" : \"\"\n }`}\n >\n <ArrowLeft className=\"h-5 w-5 shrink-0\" />\n {!isCollapsed && <span>Back to Dashboard</span>}\n </Link>\n </li>\n </ul>\n </nav>\n\n {/* Footer */}\n <div className=\"border-t border-red-500/20 px-2 pt-4\">\n <div className={`flex items-center gap-3 rounded-lg px-3 py-2 ${isCollapsed ? \"justify-center px-2\" : \"\"}`}>\n <div className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-red-500/10 text-red-600\">\n {user.name?.[0]?.toUpperCase() || \"A\"}\n </div>\n {!isCollapsed && (\n <div className=\"flex-1 overflow-hidden\">\n <p className=\"truncate text-sm font-medium\">{user.name}</p>\n <p className=\"truncate text-xs text-muted-foreground\">\n {user.role.replace(\"_\", \" \")}\n </p>\n </div>\n )}\n </div>\n <button\n onClick={() => setIsCollapsed(!isCollapsed)}\n className={`mt-2 flex w-full items-center gap-x-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors text-muted-foreground hover:bg-muted hover:text-foreground ${\n isCollapsed ? \"justify-center px-2\" : \"\"\n }`}\n >\n {isCollapsed ? (\n <ChevronsRight className=\"h-4 w-4\" />\n ) : (\n <>\n <ChevronsLeft className=\"h-4 w-4\" />\n <span>Collapse</span>\n </>\n )}\n </button>\n </div>\n </div>\n </aside>\n );\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport Link from \"next/link\";\nimport { usePathname } from \"next/navigation\";\nimport { Menu, Shield, ChevronRight } from \"lucide-react\";\n\nexport interface AdminHeaderProps {\n onMenuClick?: () => void;\n routeLabels?: Record<string, string>;\n children?: React.ReactNode;\n}\n\nconst defaultRouteLabels: Record<string, string> = {\n admin: \"Admin\",\n users: \"Users\",\n analytics: \"Analytics\",\n subscriptions: \"Subscriptions\",\n settings: \"Settings\",\n};\n\nexport function AdminHeader({\n onMenuClick,\n routeLabels = defaultRouteLabels,\n children,\n}: AdminHeaderProps) {\n const pathname = usePathname();\n\n const getBreadcrumbs = () => {\n const segments = pathname.split(\"/\").filter(Boolean);\n const breadcrumbs: { label: string; href: string; isLast: boolean }[] = [];\n\n let currentPath = \"\";\n segments.forEach((segment, index) => {\n currentPath += `/${segment}`;\n const label =\n routeLabels[segment] ||\n segment.charAt(0).toUpperCase() + segment.slice(1);\n breadcrumbs.push({\n label,\n href: currentPath,\n isLast: index === segments.length - 1,\n });\n });\n\n return breadcrumbs;\n };\n\n const breadcrumbs = getBreadcrumbs();\n\n return (\n <header className=\"flex h-16 shrink-0 items-center gap-2 border-b border-red-500/20 bg-red-500/5\">\n <div className=\"flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6\">\n {onMenuClick && (\n <button\n onClick={onMenuClick}\n className=\"inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-muted hover:text-foreground md:hidden\"\n >\n <Menu className=\"h-5 w-5\" />\n <span className=\"sr-only\">Toggle menu</span>\n </button>\n )}\n <div className=\"flex items-center gap-2 rounded-md bg-red-500/10 px-2 py-1 text-xs font-medium text-red-600 dark:text-red-400 mr-2\">\n <Shield className=\"h-3 w-3\" />\n ADMIN\n </div>\n <nav className=\"flex items-center gap-1 text-sm\">\n {breadcrumbs.map((crumb, index) => (\n <React.Fragment key={crumb.href}>\n {index > 0 && (\n <ChevronRight className=\"h-4 w-4 text-muted-foreground\" />\n )}\n {crumb.isLast ? (\n <span className=\"font-medium text-foreground\">\n {crumb.label}\n </span>\n ) : (\n <Link\n href={crumb.href}\n className=\"text-muted-foreground hover:text-foreground\"\n >\n {crumb.label}\n </Link>\n )}\n </React.Fragment>\n ))}\n </nav>\n <div className=\"flex-1\" />\n {children}\n </div>\n </header>\n );\n}\n","\"use client\";\n\nimport { type ReactNode } from \"react\";\nimport { AdminSidebar, type AdminNavItem } from \"./admin-sidebar\";\nimport { AdminHeader } from \"./admin-header\";\n\nexport interface AdminLayoutProps {\n children: ReactNode;\n user: {\n name: string;\n email: string;\n avatar?: string;\n role: string;\n };\n navItems?: AdminNavItem[];\n dashboardUrl?: string;\n headerContent?: ReactNode;\n routeLabels?: Record<string, string>;\n}\n\nexport function AdminLayout({\n children,\n user,\n navItems,\n dashboardUrl,\n headerContent,\n routeLabels,\n}: AdminLayoutProps) {\n return (\n <div className=\"min-h-screen bg-background\">\n <AdminSidebar\n user={user}\n navItems={navItems}\n dashboardUrl={dashboardUrl}\n />\n <div className=\"lg:pl-64\">\n <AdminHeader routeLabels={routeLabels}>{headerContent}</AdminHeader>\n <main className=\"p-4 sm:p-6 lg:p-8\">{children}</main>\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport type { LucideIcon } from \"lucide-react\";\n\nexport interface StatsCardProps {\n title: string;\n value: string | number;\n description?: string;\n icon?: LucideIcon;\n trend?: {\n value: number;\n isPositive: boolean;\n };\n className?: string;\n}\n\nexport function StatsCard({\n title,\n value,\n description,\n icon: Icon,\n trend,\n className,\n}: StatsCardProps) {\n return (\n <div className={`rounded-lg border bg-card p-6 ${className}`}>\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm font-medium text-muted-foreground\">{title}</p>\n {Icon && (\n <div className=\"rounded-md bg-primary/10 p-2\">\n <Icon className=\"h-4 w-4 text-primary\" />\n </div>\n )}\n </div>\n <div className=\"mt-2\">\n <p className=\"text-2xl font-bold\">{value}</p>\n {(description || trend) && (\n <div className=\"mt-1 flex items-center gap-2 text-sm\">\n {trend && (\n <span\n className={\n trend.isPositive\n ? \"text-green-600\"\n : \"text-red-600\"\n }\n >\n {trend.isPositive ? \"+\" : \"-\"}{Math.abs(trend.value)}%\n </span>\n )}\n {description && (\n <span className=\"text-muted-foreground\">{description}</span>\n )}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["React","Link","usePathname","Shield","jsx","jsxs","breadcrumbs","jsx","jsxs","jsx","jsxs"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@victusvinceere/saas-admin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Admin panel components for SaaS Kit",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./components": {
|
|
15
|
+
"types": "./dist/components/index.d.ts",
|
|
16
|
+
"import": "./dist/components/index.mjs",
|
|
17
|
+
"require": "./dist/components/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"dev": "tsup --watch",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"clean": "rm -rf dist"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@victusvinceere/saas-core": "workspace:*",
|
|
31
|
+
"next": ">=15.0.0",
|
|
32
|
+
"react": ">=19.0.0",
|
|
33
|
+
"react-dom": ">=19.0.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@tabler/icons-react": "^3.36.1",
|
|
37
|
+
"lucide-react": "^0.562.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^20",
|
|
41
|
+
"@types/react": "^19",
|
|
42
|
+
"tsup": "^8.0.2",
|
|
43
|
+
"typescript": "^5"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"saas",
|
|
47
|
+
"admin",
|
|
48
|
+
"dashboard",
|
|
49
|
+
"react"
|
|
50
|
+
],
|
|
51
|
+
"license": "MIT"
|
|
52
|
+
}
|