stackkit 0.3.5 → 0.3.6

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.
Files changed (192) hide show
  1. package/README.md +50 -42
  2. package/dist/cli/add.js +122 -56
  3. package/dist/cli/create.d.ts +2 -0
  4. package/dist/cli/create.js +271 -95
  5. package/dist/cli/doctor.js +1 -0
  6. package/dist/cli/list.d.ts +1 -1
  7. package/dist/cli/list.js +6 -4
  8. package/dist/index.js +234 -191
  9. package/dist/lib/constants.d.ts +4 -0
  10. package/dist/lib/constants.js +4 -0
  11. package/dist/lib/discovery/module-discovery.d.ts +4 -0
  12. package/dist/lib/discovery/module-discovery.js +56 -0
  13. package/dist/lib/generation/code-generator.d.ts +11 -2
  14. package/dist/lib/generation/code-generator.js +42 -3
  15. package/dist/lib/generation/generator-utils.js +3 -1
  16. package/dist/lib/pm/package-manager.js +16 -13
  17. package/dist/lib/ui/logger.js +3 -2
  18. package/dist/lib/utils/path-resolver.d.ts +2 -0
  19. package/dist/lib/utils/path-resolver.js +8 -0
  20. package/dist/meta.json +8312 -0
  21. package/modules/auth/better-auth/files/{shared → express}/config/env.ts +48 -50
  22. package/modules/auth/better-auth/files/express/middlewares/authorize.ts +20 -1
  23. package/modules/auth/better-auth/files/express/modules/auth.controller.ts +349 -0
  24. package/modules/auth/better-auth/files/express/modules/{auth/auth.route.ts → auth.route.ts} +9 -4
  25. package/modules/auth/better-auth/files/express/modules/auth.service.ts +664 -0
  26. package/modules/auth/better-auth/files/express/modules/{auth/auth.type.ts → auth.type.ts} +22 -9
  27. package/modules/auth/better-auth/files/{shared/mongoose/auth/helper.ts → express/mongo-modules/auth.helper.ts} +11 -1
  28. package/modules/auth/better-auth/files/express/types/express.d.ts +11 -0
  29. package/modules/auth/better-auth/files/nextjs/api-route.ts +74 -0
  30. package/modules/auth/better-auth/files/nextjs/dashboard/pages/(user)/page.tsx +6 -0
  31. package/modules/auth/better-auth/files/nextjs/dashboard/pages/admin/page.tsx +6 -0
  32. package/modules/auth/better-auth/files/nextjs/dashboard/pages/layout.tsx +48 -0
  33. package/modules/auth/better-auth/files/nextjs/dashboard/pages/my-profile/page.tsx +5 -0
  34. package/modules/auth/better-auth/files/nextjs/features/services/auth.service.ts +102 -0
  35. package/modules/auth/better-auth/files/nextjs/layout/layout.tsx +13 -0
  36. package/modules/auth/better-auth/files/nextjs/lib/axios/http.ts +158 -0
  37. package/modules/auth/better-auth/files/nextjs/lib/env.ts +35 -0
  38. package/modules/auth/better-auth/files/nextjs/lib/utils/auth.ts +75 -0
  39. package/modules/auth/better-auth/files/nextjs/lib/utils/cookie.ts +29 -0
  40. package/modules/auth/better-auth/files/nextjs/lib/utils/jwt.ts +28 -0
  41. package/modules/auth/better-auth/files/nextjs/lib/utils/token.ts +49 -0
  42. package/modules/auth/better-auth/files/nextjs/pages/forgot-password/page.tsx +5 -0
  43. package/modules/auth/better-auth/files/nextjs/pages/layout.tsx +11 -0
  44. package/modules/auth/better-auth/files/nextjs/pages/login/page.tsx +9 -0
  45. package/modules/auth/better-auth/files/nextjs/pages/register/page.tsx +5 -0
  46. package/modules/auth/better-auth/files/nextjs/pages/reset-password/page.tsx +10 -0
  47. package/modules/auth/better-auth/files/nextjs/pages/verify-email/page.tsx +10 -0
  48. package/modules/auth/better-auth/files/nextjs/proxy.ts +154 -42
  49. package/modules/auth/better-auth/files/nextjs/theme/providers/theme-provider.tsx +11 -0
  50. package/modules/auth/better-auth/files/nextjs/types/api.types.ts +18 -0
  51. package/modules/auth/better-auth/files/react/components/protected-route.tsx +39 -0
  52. package/modules/auth/better-auth/files/react/components/route-guards.tsx +13 -0
  53. package/modules/auth/better-auth/files/react/dashboard/admin/pages/overview.tsx +3 -0
  54. package/modules/auth/better-auth/files/react/dashboard/pages/overview.tsx +3 -0
  55. package/modules/auth/better-auth/files/react/features/pages/forgot-password.tsx +5 -0
  56. package/modules/auth/better-auth/files/react/features/pages/login.tsx +5 -0
  57. package/modules/auth/better-auth/files/react/features/pages/my-profile.tsx +5 -0
  58. package/modules/auth/better-auth/files/react/features/pages/oauth-callback.tsx +59 -0
  59. package/modules/auth/better-auth/files/react/features/pages/register.tsx +5 -0
  60. package/modules/auth/better-auth/files/react/features/pages/reset-password.tsx +10 -0
  61. package/modules/auth/better-auth/files/react/features/pages/verify-email.tsx +10 -0
  62. package/modules/auth/better-auth/files/react/layout/dashboard-layout.tsx +54 -0
  63. package/modules/auth/better-auth/files/react/lib/axios/http.ts +68 -0
  64. package/modules/auth/better-auth/files/react/lib/env.ts +25 -0
  65. package/modules/auth/better-auth/files/react/router.tsx +73 -0
  66. package/modules/auth/better-auth/files/react/theme/components/providers/theme-provider-context.ts +13 -0
  67. package/modules/auth/better-auth/files/react/theme/components/providers/theme-provider.tsx +51 -0
  68. package/modules/auth/better-auth/files/react/theme/hooks/use-theme.ts +8 -0
  69. package/modules/auth/better-auth/files/shared/features/components/change-password-dialog.tsx +113 -0
  70. package/modules/auth/better-auth/files/shared/features/components/forgot-password-form.tsx +84 -0
  71. package/modules/auth/better-auth/files/shared/features/components/login-form.tsx +134 -0
  72. package/modules/auth/better-auth/files/shared/features/components/my-profile.tsx +147 -0
  73. package/modules/auth/better-auth/files/shared/features/components/profile-form.tsx +205 -0
  74. package/modules/auth/better-auth/files/shared/features/components/register-form.tsx +100 -0
  75. package/modules/auth/better-auth/files/shared/features/components/reset-password-form.tsx +111 -0
  76. package/modules/auth/better-auth/files/shared/features/components/social-login-buttons.tsx +47 -0
  77. package/modules/auth/better-auth/files/shared/features/components/user-profile-menu.tsx +106 -0
  78. package/modules/auth/better-auth/files/shared/features/components/verify-email-form.tsx +110 -0
  79. package/modules/auth/better-auth/files/shared/features/queries/auth.mutations.tsx +312 -0
  80. package/modules/auth/better-auth/files/shared/features/queries/auth.querie.ts +19 -0
  81. package/modules/auth/better-auth/files/shared/features/services/auth.api.ts +81 -0
  82. package/modules/auth/better-auth/files/shared/features/types/auth.type.ts +47 -0
  83. package/modules/auth/better-auth/files/shared/features/validators/change-password.validator.ts +18 -0
  84. package/modules/auth/better-auth/files/shared/features/validators/forgot.validator.ts +7 -0
  85. package/modules/auth/better-auth/files/shared/features/validators/login.validator.ts +14 -0
  86. package/modules/auth/better-auth/files/shared/features/validators/profile.validator.ts +8 -0
  87. package/modules/auth/better-auth/files/shared/features/validators/register.validator.ts +9 -0
  88. package/modules/auth/better-auth/files/shared/features/validators/reset.validator.ts +9 -0
  89. package/modules/auth/better-auth/files/shared/features/validators/verify.validator.ts +8 -0
  90. package/modules/auth/better-auth/files/shared/lib/auth-client.ts +2 -1
  91. package/modules/auth/better-auth/files/shared/lib/auth.ts +5 -19
  92. package/modules/auth/better-auth/files/shared/lib/constant/dashboard.ts +90 -0
  93. package/modules/auth/better-auth/files/shared/theme/mode-toggle.tsx +30 -0
  94. package/modules/auth/better-auth/files/shared/ui/shadcn/components/dashboard/dashboard-header.tsx +94 -0
  95. package/modules/auth/better-auth/files/shared/ui/shadcn/components/dashboard/dashboard-sidebar.tsx +255 -0
  96. package/modules/auth/better-auth/files/shared/ui/shadcn/components/footer.tsx +35 -0
  97. package/modules/auth/better-auth/files/shared/ui/shadcn/components/navbar.tsx +145 -0
  98. package/modules/auth/better-auth/files/shared/ui/shadcn/form-field/input-field.tsx +440 -0
  99. package/modules/auth/better-auth/files/shared/utils/email.ts +2 -17
  100. package/modules/auth/better-auth/generator.json +172 -51
  101. package/modules/auth/better-auth/module.json +2 -2
  102. package/modules/components/files/shared/hooks/use-file-upload.ts +412 -0
  103. package/modules/components/files/shared/lib/utils/url-helpers.ts +110 -0
  104. package/modules/components/files/shared/shadcn/dashboard/data-table-column-selector.tsx +52 -0
  105. package/modules/components/files/shared/shadcn/dashboard/data-table-footer.tsx +156 -0
  106. package/modules/components/files/shared/shadcn/dashboard/data-table.tsx +405 -0
  107. package/modules/components/files/shared/shadcn/global/form-field/input-field.tsx +440 -0
  108. package/modules/components/files/shared/shadcn/global/form-field/media-uploader-field.tsx +745 -0
  109. package/modules/components/files/shared/shadcn/global/form-field/multi-select-field.tsx +207 -0
  110. package/modules/components/files/shared/shadcn/global/form-field/select-field.tsx +247 -0
  111. package/modules/components/files/shared/shadcn/global/form-field/textarea-field.tsx +277 -0
  112. package/modules/components/files/shared/shadcn/global/form-field/tiptap-editor-field.tsx +35 -0
  113. package/modules/components/files/shared/shadcn/global/no-results.tsx +41 -0
  114. package/modules/components/files/shared/shadcn/tiptap-editor/editor-menu-bar.tsx +217 -0
  115. package/modules/components/files/shared/shadcn/tiptap-editor/tiptap-editor.tsx +104 -0
  116. package/modules/components/files/shared/url/load-more.tsx +93 -0
  117. package/modules/components/files/shared/url/search-bar.tsx +131 -0
  118. package/modules/components/files/shared/url/sort-select.tsx +118 -0
  119. package/modules/components/files/shared/url/url-tabs.tsx +77 -0
  120. package/modules/components/generator.json +109 -0
  121. package/modules/components/module.json +11 -0
  122. package/modules/database/mongoose/generator.json +3 -14
  123. package/modules/database/mongoose/module.json +2 -2
  124. package/modules/database/prisma/generator.json +6 -12
  125. package/modules/database/prisma/module.json +2 -2
  126. package/modules/storage/cloudinary/files/express/config/env.ts +65 -0
  127. package/modules/storage/cloudinary/files/express/config/media.ts +103 -0
  128. package/modules/storage/cloudinary/files/express/modules/media/media.controller.ts +59 -0
  129. package/modules/storage/cloudinary/files/express/modules/media/media.route.ts +29 -0
  130. package/modules/storage/cloudinary/files/express/modules/media/media.service.ts +113 -0
  131. package/modules/storage/cloudinary/files/express/modules/media/media.type.ts +32 -0
  132. package/modules/storage/cloudinary/generator.json +34 -0
  133. package/modules/storage/cloudinary/module.json +11 -0
  134. package/modules/ui/shadcn/generator.json +21 -0
  135. package/modules/ui/shadcn/module.json +11 -0
  136. package/package.json +24 -26
  137. package/templates/express/README.md +11 -16
  138. package/templates/express/src/config/env.ts +7 -5
  139. package/templates/nextjs/README.md +13 -18
  140. package/templates/nextjs/app/favicon.ico +0 -0
  141. package/templates/nextjs/app/layout.tsx +6 -4
  142. package/templates/nextjs/components/providers/query-provider.tsx +3 -0
  143. package/templates/nextjs/env.example +3 -1
  144. package/templates/nextjs/lib/axios/http.ts +23 -0
  145. package/templates/nextjs/lib/env.ts +7 -5
  146. package/templates/nextjs/package.json +2 -1
  147. package/templates/nextjs/template.json +1 -2
  148. package/templates/react/README.md +9 -14
  149. package/templates/react/index.html +1 -1
  150. package/templates/react/package.json +1 -1
  151. package/templates/react/src/assets/favicon.ico +0 -0
  152. package/templates/react/src/components/providers/query-provider.tsx +38 -0
  153. package/templates/react/src/{shared/components → components}/seo.tsx +4 -8
  154. package/templates/react/src/lib/axios/http.ts +24 -0
  155. package/templates/react/src/main.tsx +8 -11
  156. package/templates/react/src/{features/about/pages → pages}/about.tsx +1 -1
  157. package/templates/react/src/{features/home/pages → pages}/home.tsx +1 -1
  158. package/templates/react/src/router.tsx +6 -6
  159. package/templates/react/src/vite-env.d.ts +2 -1
  160. package/templates/react/template.json +0 -1
  161. package/templates/react/tsconfig.app.json +6 -0
  162. package/templates/react/tsconfig.json +7 -1
  163. package/templates/react/vite.config.ts +12 -0
  164. package/modules/auth/authjs/files/nextjs/api/auth/[...nextauth]/route.ts +0 -3
  165. package/modules/auth/authjs/files/nextjs/proxy.ts +0 -1
  166. package/modules/auth/authjs/files/shared/lib/auth.ts +0 -119
  167. package/modules/auth/authjs/files/shared/prisma/schema.prisma +0 -61
  168. package/modules/auth/authjs/generator.json +0 -64
  169. package/modules/auth/authjs/module.json +0 -13
  170. package/modules/auth/better-auth/files/express/modules/auth/auth.controller.ts +0 -264
  171. package/modules/auth/better-auth/files/express/modules/auth/auth.service.ts +0 -549
  172. package/modules/auth/better-auth/files/express/templates/google-redirect.ejs +0 -24
  173. package/modules/auth/better-auth/files/nextjs/api/auth/[...all]/route.ts +0 -4
  174. package/modules/auth/better-auth/files/nextjs/lib/auth/auth-guards.ts +0 -31
  175. package/modules/auth/better-auth/files/nextjs/templates/email-otp.tsx +0 -74
  176. package/templates/nextjs/lib/api/http.ts +0 -40
  177. package/templates/react/public/vite.svg +0 -1
  178. package/templates/react/src/app/layouts/dashboard-layout.tsx +0 -8
  179. package/templates/react/src/app/layouts/public-layout.tsx +0 -5
  180. package/templates/react/src/app/providers.tsx +0 -20
  181. package/templates/react/src/app/router.tsx +0 -21
  182. package/templates/react/src/assets/react.svg +0 -1
  183. package/templates/react/src/shared/api/http.ts +0 -39
  184. package/templates/react/src/shared/components/loading.tsx +0 -8
  185. package/templates/react/src/shared/lib/query-client.ts +0 -12
  186. package/templates/react/src/utils/storage.ts +0 -35
  187. package/templates/react/src/utils/utils.ts +0 -3
  188. /package/modules/auth/better-auth/files/{shared/mongoose/auth/constants.ts → express/mongo-modules/auth.constants.ts} +0 -0
  189. /package/templates/nextjs/app/{page.tsx → (public)/(root)/page.tsx} +0 -0
  190. /package/templates/react/src/{shared/components → components}/error-boundary.tsx +0 -0
  191. /package/templates/react/src/{shared/components → components}/layout.tsx +0 -0
  192. /package/templates/react/src/{shared/pages → pages}/not-found.tsx +0 -0
@@ -0,0 +1,90 @@
1
+ import type {
2
+ DashboardSidebarMenuGroup,
3
+ IconMapKey,
4
+ } from "@/components/dashboard/dashboard-sidebar";
5
+
6
+ export const ADMIN: DashboardSidebarMenuGroup[] = [
7
+ {
8
+ label: "Main",
9
+ items: [
10
+ {
11
+ title: "Dashboard",
12
+ url: "/dashboard/admin",
13
+ icon: "LayoutDashboard" as IconMapKey,
14
+ },
15
+ {
16
+ title: "Users",
17
+ url: "/dashboard/admin/users",
18
+ icon: "Users" as IconMapKey,
19
+ },
20
+ {
21
+ title: "Content",
22
+ url: "/dashboard/admin/content",
23
+ icon: "Package" as IconMapKey,
24
+ },
25
+ {
26
+ title: "Categories",
27
+ url: "/dashboard/admin/categories",
28
+ icon: "Tag" as IconMapKey,
29
+ },
30
+ ],
31
+ },
32
+ {
33
+ label: "Media",
34
+ items: [
35
+ {
36
+ title: "Images",
37
+ url: "/dashboard/admin/media/images",
38
+ icon: "Image" as IconMapKey,
39
+ },
40
+ {
41
+ title: "Videos",
42
+ url: "/dashboard/admin/media/videos",
43
+ icon: "Clapperboard" as IconMapKey,
44
+ },
45
+ ],
46
+ },
47
+ {
48
+ label: "Security & Support",
49
+ items: [
50
+ {
51
+ title: "Roles & Permissions",
52
+ url: "/dashboard/admin/roles",
53
+ icon: "Shield" as IconMapKey,
54
+ },
55
+ {
56
+ title: "Support",
57
+ url: "/dashboard/admin/support",
58
+ icon: "LifeBuoy" as IconMapKey,
59
+ },
60
+ ],
61
+ },
62
+ ];
63
+
64
+ export const USER: DashboardSidebarMenuGroup[] = [
65
+ {
66
+ label: "Navigation",
67
+ items: [
68
+ {
69
+ title: "Dashboard",
70
+ url: "/dashboard",
71
+ icon: "LayoutDashboard" as IconMapKey,
72
+ },
73
+ {
74
+ title: "Activity",
75
+ url: "/dashboard/activity",
76
+ icon: "List" as IconMapKey,
77
+ },
78
+ {
79
+ title: "Saved",
80
+ url: "/dashboard/saved",
81
+ icon: "Heart" as IconMapKey,
82
+ },
83
+ ],
84
+ },
85
+ ];
86
+
87
+ export const sidebar = {
88
+ ADMIN,
89
+ USER,
90
+ };
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import { MoonIcon, SunIcon } from "lucide-react";
5
+ {{#if framework == "nextjs"}}
6
+ import { useTheme } from "next-themes";
7
+ {{else}}
8
+ import { useTheme } from "../hooks/use-theme";
9
+ {{/if}}
10
+
11
+ export function ModeToggle() {
12
+ const { theme, setTheme } = useTheme();
13
+
14
+ const toggleTheme = () => {
15
+ setTheme(theme === "dark" ? "light" : "dark");
16
+ };
17
+
18
+ return (
19
+ <Button
20
+ variant="ghost"
21
+ size="icon"
22
+ onClick={toggleTheme}
23
+ className="cursor-pointer"
24
+ aria-label="Toggle theme"
25
+ >
26
+ <SunIcon className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
27
+ <MoonIcon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
28
+ </Button>
29
+ );
30
+ }
@@ -0,0 +1,94 @@
1
+ "use client";
2
+
3
+ import { Separator } from "@/components/ui/separator";
4
+ import { SidebarTrigger } from "@/components/ui/sidebar";
5
+ import { cn } from "@/lib/utils";
6
+ import { useEffect, useState } from "react";
7
+ import { ModeToggle } from "../mode-toggle";
8
+ import { Badge } from "../ui/badge";
9
+
10
+ export default function DashboardHeader({ role }: { role: "USER" | "ADMIN" | undefined }) {
11
+ const [isOnline, setIsOnline] = useState(
12
+ typeof window !== "undefined" ? navigator.onLine : true,
13
+ );
14
+ const pulse = true;
15
+ const subtle = true;
16
+
17
+ // Online/Offline
18
+ useEffect(() => {
19
+ const on = () => setIsOnline(true);
20
+ const off = () => setIsOnline(false);
21
+ window.addEventListener("online", on);
22
+ window.addEventListener("offline", off);
23
+ return () => {
24
+ window.removeEventListener("online", on);
25
+ window.removeEventListener("offline", off);
26
+ };
27
+ }, []);
28
+
29
+ return (
30
+ <header className="border-border bg-card/80 supports-backdrop-filter:bg-card/60 sticky top-0 z-40 w-full border-b backdrop-blur">
31
+ <nav className="mx-auto flex max-w-screen-2xl items-center justify-between gap-4 px-4 py-3">
32
+ {/* Left */}
33
+ <div className="flex items-center gap-3">
34
+ <SidebarTrigger className="cursor-pointer" />
35
+
36
+ {/* Divider */}
37
+ <Separator orientation="vertical" className="mx-1 h-6" />
38
+
39
+ {/* System Status */}
40
+ {role === "ADMIN" && (
41
+ <Badge
42
+ variant="secondary"
43
+ aria-live="polite"
44
+ aria-atomic="true"
45
+ className={cn(
46
+ "inline-flex items-center gap-2 rounded-full px-3.5 py-1.5 text-xs font-semibold shadow-sm ring-1 ring-inset",
47
+ subtle
48
+ ? isOnline
49
+ ? "bg-primary/15 text-primary ring-primary/30"
50
+ : "bg-destructive/15 text-destructive ring-destructive/30"
51
+ : isOnline
52
+ ? "bg-primary/10 text-primary ring-primary/25"
53
+ : "bg-destructive/10 text-destructive ring-destructive/25",
54
+ "transition-colors",
55
+ )}
56
+ title={`System ${isOnline ? "Online" : "Offline"}`}
57
+ >
58
+ {/* Dot + optional ping */}
59
+ <span className="relative flex h-2.5 w-2.5 items-center justify-center">
60
+ {pulse && (
61
+ <span
62
+ className={cn(
63
+ "absolute inline-flex h-full w-full animate-ping rounded-full opacity-70",
64
+ isOnline ? "bg-primary" : "bg-destructive",
65
+ )}
66
+ />
67
+ )}
68
+ <span
69
+ className={cn(
70
+ "relative inline-flex h-2.5 w-2.5 rounded-full",
71
+ isOnline ? "bg-primary" : "bg-destructive",
72
+ )}
73
+ />
74
+ </span>
75
+
76
+ {/* Text */}
77
+ <span className="tracking-wide">
78
+ System{" "}
79
+ <span className="font-bold">
80
+ {isOnline ? "Online" : "Offline"}
81
+ </span>
82
+ </span>
83
+ </Badge>
84
+ )}
85
+ </div>
86
+
87
+ {/* Right */}
88
+ <div className="flex items-center gap-3">
89
+ <ModeToggle />
90
+ </div>
91
+ </nav>
92
+ </header>
93
+ );
94
+ }
@@ -0,0 +1,255 @@
1
+ {{#if framework == "nextjs"}}
2
+ "use client";
3
+ {{/if}}
4
+
5
+ import {
6
+ ChevronRight,
7
+ Clapperboard,
8
+ Heart,
9
+ Image,
10
+ LayoutDashboard,
11
+ LifeBuoy,
12
+ List,
13
+ MoreVertical,
14
+ Package,
15
+ Shield,
16
+ ShoppingCart,
17
+ Tag,
18
+ Users,
19
+ } from "lucide-react";
20
+ {{#if framework == "nextjs"}}
21
+ import Link from "next/link";
22
+ import { usePathname } from "next/navigation";
23
+ {{else}}
24
+ import { Link, useLocation } from "react-router";
25
+ {{/if}}
26
+
27
+ import {
28
+ Sidebar,
29
+ SidebarContent,
30
+ SidebarFooter,
31
+ SidebarGroup,
32
+ SidebarGroupContent,
33
+ SidebarGroupLabel,
34
+ SidebarHeader,
35
+ SidebarMenu,
36
+ SidebarMenuButton,
37
+ SidebarMenuItem,
38
+ useSidebar,
39
+ } from "@/components/ui/sidebar";
40
+ import { useLogoutMutation } from "@/features/auth/queries/auth.mutations";
41
+ import { cn } from "@/lib/utils";
42
+ import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
43
+ import {
44
+ DropdownMenu,
45
+ DropdownMenuContent,
46
+ DropdownMenuGroup,
47
+ DropdownMenuItem,
48
+ DropdownMenuLabel,
49
+ DropdownMenuSeparator,
50
+ DropdownMenuTrigger,
51
+ } from "../ui/dropdown-menu";
52
+ import { Separator } from "../ui/separator";
53
+
54
+
55
+ const iconMap = {
56
+ LayoutDashboard,
57
+ ShoppingCart,
58
+ Package,
59
+ Tag,
60
+ Users,
61
+ Shield,
62
+ LifeBuoy,
63
+ List,
64
+ Heart,
65
+ Image,
66
+ Clapperboard,
67
+ } as const;
68
+ export type IconMapKey = keyof typeof iconMap;
69
+
70
+ export interface DashboardSidebarMenuItem {
71
+ title: string;
72
+ url: string;
73
+ icon: IconMapKey;
74
+ }
75
+
76
+ export interface DashboardSidebarMenuGroup {
77
+ label: string;
78
+ items: DashboardSidebarMenuItem[];
79
+ }
80
+
81
+ export interface DashboardSidebarProps {
82
+ menu: DashboardSidebarMenuItem[] | DashboardSidebarMenuGroup[];
83
+ user: {
84
+ id?: string;
85
+ name?: string | null;
86
+ email?: string | null;
87
+ image?: string | null;
88
+ role?: string;
89
+ };
90
+ }
91
+
92
+ export function DashboardSidebar({ menu = [], user }: DashboardSidebarProps) {
93
+ {{#if framework == "nextjs"}}
94
+ const pathname = usePathname();
95
+ {{else}}
96
+ const { pathname } = useLocation();
97
+ {{/if}}
98
+ const { toggleSidebar } = useSidebar();
99
+ const { mutate: logout, isPending } = useLogoutMutation();
100
+
101
+ const isActive = (url: string) =>
102
+ url === "/dashboard" ||
103
+ url === "/dashboard/admin"
104
+ ? pathname === url
105
+ : pathname.startsWith(url);
106
+
107
+ const groupedMenu = (
108
+ menu.length > 0 && "items" in menu[0]
109
+ ? (menu as DashboardSidebarMenuGroup[])
110
+ : [{ label: "Navigation", items: menu as DashboardSidebarMenuItem[] }]
111
+ ).filter((group) => group.items.length > 0);
112
+
113
+ return (
114
+ <Sidebar className="bg-linear-to-b from-background to-muted/30 backdrop-blur supports-backdrop-filter:bg-background/80">
115
+ <SidebarHeader className="px-3 py-2">
116
+ <div className="flex items-center justify-between">
117
+ {{#if framework == "nextjs"}}
118
+ <Link href="/" className="text-lg font-bold">
119
+ StackKit
120
+ </Link>
121
+ {{else}}
122
+ <Link to="/" className="text-lg font-bold">
123
+ StackKit
124
+ </Link>
125
+ {{/if}}
126
+ {/* Collapser for small screens */}
127
+ <button
128
+ onClick={toggleSidebar}
129
+ className="rounded-md p-1.5 text-muted-foreground hover:bg-muted/60 hover:text-foreground lg:hidden"
130
+ aria-label="Toggle sidebar"
131
+ >
132
+ <ChevronRight className="h-4 w-4" />
133
+ </button>
134
+ </div>
135
+ </SidebarHeader>
136
+
137
+ <Separator />
138
+
139
+ <SidebarContent>
140
+ {groupedMenu.map((group) => (
141
+ <SidebarGroup key={group.label}>
142
+ <SidebarGroupLabel className="text-xs font-medium tracking-wide text-muted-foreground">
143
+ {group.label}
144
+ </SidebarGroupLabel>
145
+ <SidebarGroupContent>
146
+ <SidebarMenu>
147
+ {group.items.map((item) => {
148
+ const Icon = iconMap[item.icon] ?? LayoutDashboard;
149
+ const active = isActive(item.url);
150
+ return (
151
+ <SidebarMenuItem key={item.title}>
152
+ <SidebarMenuButton
153
+ tooltip={item.title}
154
+ className={cn(
155
+ "hover:bg-muted dark:hover:bg-muted/80",
156
+ active && "bg-muted dark:bg-muted/80",
157
+ )}
158
+ >
159
+ {{#if framework == "nextjs"}}
160
+ <Link
161
+ href={item.url || "#"}
162
+ onClick={() => {
163
+ if (
164
+ typeof window !== "undefined" &&
165
+ window.innerWidth < 768
166
+ ) {
167
+ toggleSidebar();
168
+ }
169
+ }}
170
+ >
171
+ <Icon className="h-4 w-4" />
172
+ <span>{item.title}</span>
173
+ </Link>
174
+ {{else}}
175
+ <Link
176
+ to={item.url || "/"}
177
+ onClick={() => {
178
+ if (
179
+ typeof window !== "undefined" &&
180
+ window.innerWidth < 768
181
+ ) {
182
+ toggleSidebar();
183
+ }
184
+ }}
185
+ >
186
+ <Icon className="h-4 w-4" />
187
+ <span>{item.title}</span>
188
+ </Link>
189
+ {{/if}}
190
+ </SidebarMenuButton>
191
+ </SidebarMenuItem>
192
+ );
193
+ })}
194
+ </SidebarMenu>
195
+ </SidebarGroupContent>
196
+ </SidebarGroup>
197
+ ))}
198
+ </SidebarContent>
199
+
200
+ <Separator className="mt-auto" />
201
+
202
+ <SidebarFooter className="border-t bg-muted/40 p-3">
203
+ <DropdownMenu>
204
+ <DropdownMenuTrigger className="flex w-full items-center justify-between gap-3 rounded-lg px-2 py-1.5 text-left hover:bg-muted">
205
+ <span className="flex items-center gap-3">
206
+ <span className="relative">
207
+ <Avatar className="h-9 w-9">
208
+ <AvatarImage
209
+ src={user?.image || "/assets/images/logo.png"}
210
+ alt={user?.name || "User"}
211
+ className="object-cover"
212
+ />
213
+ <AvatarFallback>
214
+ {user?.name?.toUpperCase().charAt(0) || "U"}
215
+ </AvatarFallback>
216
+ </Avatar>
217
+ <span
218
+ className="absolute -right-0.5 -top-0.5 block h-2.5 w-2.5 rounded-full border-2 border-background bg-primary"
219
+ aria-label="Online"
220
+ />
221
+ </span>
222
+ <span className="min-w-0">
223
+ <span className="block truncate text-sm font-medium">
224
+ {user?.name || "Admin"}
225
+ </span>
226
+ <span className="block truncate text-xs text-muted-foreground">
227
+ {user?.email}
228
+ </span>
229
+ </span>
230
+ </span>
231
+ <MoreVertical className="h-4 w-4 text-muted-foreground" />
232
+ </DropdownMenuTrigger>
233
+ <DropdownMenuContent side="top" align="end" className="w-56">
234
+ <DropdownMenuGroup>
235
+ <DropdownMenuLabel>Account</DropdownMenuLabel>
236
+ <DropdownMenuItem>
237
+ {{#if framework == "nextjs"}}
238
+ <Link href="/dashboard/my-profile">Profile</Link>
239
+ {{else}}
240
+ <Link to="/dashboard/my-profile">Profile</Link>
241
+ {{/if}}
242
+ </DropdownMenuItem>
243
+ </DropdownMenuGroup>
244
+ <DropdownMenuSeparator />
245
+ <DropdownMenuItem disabled={isPending} onClick={() => logout()}>
246
+ Log out
247
+ </DropdownMenuItem>
248
+ </DropdownMenuContent>
249
+ </DropdownMenu>
250
+ </SidebarFooter>
251
+ </Sidebar>
252
+ );
253
+ }
254
+
255
+ export default DashboardSidebar;
@@ -0,0 +1,35 @@
1
+ {{#if framework == "nextjs"}}
2
+ import Link from "next/link";
3
+ {{else}}
4
+ import { Link } from "react-router";
5
+ {{/if}}
6
+
7
+ const footerLinks = [
8
+ { name: "Privacy", href: "#" },
9
+ { name: "Terms", href: "#" },
10
+ ];
11
+
12
+ export default function Footer() {
13
+ return (
14
+ <footer className="w-full bg-background border-t mt-8">
15
+ <div className="mx-auto max-w-7xl px-4 py-6 flex items-center justify-between text-sm text-muted-foreground">
16
+ <div>© {new Date().getFullYear()} StackKit</div>
17
+ <div className="flex gap-4">
18
+ {{#if framework == "nextjs"}}
19
+ {footerLinks.map((link, idx) => (
20
+ <Link key={idx} href={link.href} className="underline">
21
+ {link.name}
22
+ </Link>
23
+ ))}
24
+ {{else}}
25
+ {footerLinks.map((link, idx) => (
26
+ <Link key={idx} to={link.href} className="underline">
27
+ {link.name}
28
+ </Link>
29
+ ))}
30
+ {{/if}}
31
+ </div>
32
+ </div>
33
+ </footer>
34
+ );
35
+ }
@@ -0,0 +1,145 @@
1
+ {{#if framework == "nextjs"}}
2
+ "use client";
3
+ {{/if}}
4
+
5
+ import { ModeToggle } from "@/components/mode-toggle";
6
+ import { Button } from "@/components/ui/button";
7
+ import {
8
+ Sheet,
9
+ SheetContent,
10
+ SheetDescription,
11
+ SheetHeader,
12
+ SheetTitle,
13
+ SheetTrigger,
14
+ } from "@/components/ui/sheet";
15
+ import UserProfile from "@/features/auth/components/user-profile-menu";
16
+ {{#if framework == "nextjs"}}
17
+ import Link from "next/link";
18
+ import { usePathname } from "next/navigation";
19
+ {{else}}
20
+ import { Link, NavLink } from "react-router";
21
+ {{/if}}
22
+
23
+ const navLinks = [
24
+ { name: "Home", href: "/" },
25
+ { name: "Docs", href: "/docs" },
26
+ ];
27
+
28
+ export default function Navbar() {
29
+ {{#if framework == "nextjs"}}
30
+ const pathname = usePathname();
31
+ {{/if}}
32
+
33
+ return (
34
+ <header className="w-full border-b">
35
+ <div className="mx-auto max-w-5xl px-4 py-4 flex items-center justify-between">
36
+ {{#if framework == "nextjs"}}
37
+ <Link href="/" className="text-lg font-bold">
38
+ StackKit
39
+ </Link>
40
+ {{else}}
41
+ <Link to="/" className="text-lg font-bold">
42
+ StackKit
43
+ </Link>
44
+ {{/if}}
45
+
46
+ <nav className="relative flex items-center">
47
+ <div className="hidden md:flex items-center gap-4">
48
+ {{#if framework == "nextjs"}}
49
+ {navLinks.map((link) => (
50
+ <Link
51
+ key={link.href}
52
+ href={link.href}
53
+ className={`text-sm ${pathname === link.href ? "active" : "text-muted-foreground"}`}
54
+ >
55
+ {link.name}
56
+ </Link>
57
+ ))}
58
+ {{else}}
59
+ {navLinks.map((link) => (
60
+ <NavLink
61
+ key={link.href}
62
+ to={link.href}
63
+ className={({ isActive }) =>
64
+ `text-sm ${isActive ? "active" : "text-muted-foreground"}`
65
+ }
66
+ >
67
+ {link.name}
68
+ </NavLink>
69
+ ))}
70
+ {{/if}}
71
+ </div>
72
+
73
+ {/* Desktop actions */}
74
+ <div className="hidden md:flex items-center gap-4 ml-4">
75
+ <UserProfile />
76
+ <ModeToggle />
77
+ </div>
78
+
79
+ {/* Mobile sheet (shadcn) */}
80
+ <div className="md:hidden ml-2">
81
+ <Sheet>
82
+ <SheetTrigger
83
+ render={
84
+ <Button variant="ghost" size="icon" aria-label="Open menu" />
85
+ }
86
+ >
87
+ <svg
88
+ xmlns="http://www.w3.org/2000/svg"
89
+ fill="none"
90
+ viewBox="0 0 24 24"
91
+ strokeWidth={2}
92
+ stroke="currentColor"
93
+ className="size-4"
94
+ >
95
+ <path
96
+ strokeLinecap="round"
97
+ strokeLinejoin="round"
98
+ d="M3 12h18M3 6h18M3 18h18"
99
+ />
100
+ </svg>
101
+ </SheetTrigger>
102
+
103
+ <SheetContent side="right" showCloseButton>
104
+ <SheetHeader>
105
+ <SheetTitle>Menu</SheetTitle>
106
+ <SheetDescription>Navigate the site</SheetDescription>
107
+ </SheetHeader>
108
+
109
+ <div className="flex flex-col gap-3 p-2">
110
+ {{#if framework == "nextjs"}}
111
+ {navLinks.map((link) => (
112
+ <Link
113
+ key={link.href}
114
+ href={link.href}
115
+ className={`px-3 py-2 rounded-md text-sm hover:bg-muted ${pathname === link.href ? "active" : ""}`}
116
+ >
117
+ {link.name}
118
+ </Link>
119
+ ))}
120
+ {{else}}
121
+ {navLinks.map((link) => (
122
+ <NavLink
123
+ key={link.href}
124
+ to={link.href}
125
+ className={({ isActive }) =>
126
+ `px-3 py-2 rounded-md text-sm hover:bg-muted ${isActive ? "active" : ""}`
127
+ }
128
+ >
129
+ {link.name}
130
+ </NavLink>
131
+ ))}
132
+ {{/if}}
133
+
134
+ <div className="border-t my-2" />
135
+ <UserProfile />
136
+ <ModeToggle />
137
+ </div>
138
+ </SheetContent>
139
+ </Sheet>
140
+ </div>
141
+ </nav>
142
+ </div>
143
+ </header>
144
+ );
145
+ }