create-mantiq 0.7.0 → 0.7.2

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 (220) hide show
  1. package/package.json +2 -1
  2. package/skeleton/.env.example +64 -0
  3. package/skeleton/README.md +46 -0
  4. package/skeleton/app/Console/Commands/.gitkeep +0 -0
  5. package/skeleton/app/Enums/UserStatus.ts +7 -0
  6. package/skeleton/app/Http/Controllers/HomeController.ts +78 -0
  7. package/skeleton/app/Http/Middleware/.gitkeep +0 -0
  8. package/skeleton/app/Models/User.ts +7 -0
  9. package/skeleton/app/Providers/AppServiceProvider.ts +25 -0
  10. package/skeleton/app/Providers/DatabaseServiceProvider.ts +17 -0
  11. package/skeleton/bootstrap/.gitkeep +0 -0
  12. package/skeleton/config/ai.ts +51 -0
  13. package/skeleton/config/app.ts +108 -0
  14. package/skeleton/config/auth.ts +51 -0
  15. package/skeleton/config/broadcasting.ts +93 -0
  16. package/skeleton/config/cache.ts +61 -0
  17. package/skeleton/config/cors.ts +77 -0
  18. package/skeleton/config/database.ts +120 -0
  19. package/skeleton/config/filesystem.ts +58 -0
  20. package/skeleton/config/hashing.ts +47 -0
  21. package/skeleton/config/heartbeat.ts +112 -0
  22. package/skeleton/config/logging.ts +58 -0
  23. package/skeleton/config/mail.ts +93 -0
  24. package/skeleton/config/notify.ts +141 -0
  25. package/skeleton/config/queue.ts +59 -0
  26. package/skeleton/config/search.ts +96 -0
  27. package/skeleton/config/services.ts +110 -0
  28. package/skeleton/config/session.ts +84 -0
  29. package/skeleton/config/vite.ts +33 -0
  30. package/skeleton/database/factories/.gitkeep +0 -0
  31. package/skeleton/database/migrations/001_create_users_table.ts +19 -0
  32. package/skeleton/database/migrations/002_create_personal_access_tokens_table.ts +22 -0
  33. package/skeleton/database/seeders/DatabaseSeeder.ts +7 -0
  34. package/skeleton/index.ts +20 -0
  35. package/skeleton/mantiq.ts +8 -0
  36. package/skeleton/package.json +34 -0
  37. package/skeleton/public/.gitkeep +0 -0
  38. package/skeleton/routes/api.ts +8 -0
  39. package/skeleton/routes/channels.ts +23 -0
  40. package/skeleton/routes/console.ts +24 -0
  41. package/skeleton/routes/web.ts +6 -0
  42. package/skeleton/storage/cache/.gitkeep +0 -0
  43. package/skeleton/storage/framework/.gitkeep +0 -0
  44. package/skeleton/tests/feature/api.test.ts +14 -0
  45. package/skeleton/tests/feature/home.test.ts +17 -0
  46. package/skeleton/tests/unit/example.test.ts +11 -0
  47. package/skeleton/tsconfig.json +27 -0
  48. package/src/index.ts +289 -25
  49. package/src/templates.ts +141 -945
  50. package/src/terminal.ts +64 -0
  51. package/stubs/api-only/routes/api.ts.stub +24 -0
  52. package/stubs/api-only/tests/feature/token-auth.test.ts.stub +69 -0
  53. package/stubs/auth/api/app/Http/Controllers/ApiAuthController.ts.stub +57 -0
  54. package/stubs/auth/api/routes/api.ts.stub +24 -0
  55. package/stubs/auth/api/tests/feature/token-auth.test.ts.stub +69 -0
  56. package/stubs/auth/shared/app/Http/Requests/LoginRequest.ts.stub +10 -0
  57. package/stubs/auth/shared/app/Http/Requests/RegisterRequest.ts.stub +11 -0
  58. package/stubs/auth/web/app/Http/Controllers/AuthController.ts.stub +43 -0
  59. package/stubs/auth/web/app/Http/Controllers/PageController.ts.stub +66 -0
  60. package/stubs/auth/web/routes/web.ts.stub +25 -0
  61. package/stubs/auth/web/svelte/src/App.svelte.stub +77 -0
  62. package/stubs/auth/web/svelte/src/pages.ts.stub +17 -0
  63. package/stubs/auth/web/tests/feature/auth.test.ts.stub +69 -0
  64. package/stubs/auth/web/vue/src/App.vue.stub +74 -0
  65. package/stubs/auth/web/vue/src/pages.ts.stub +17 -0
  66. package/stubs/manifest.json +630 -2
  67. package/stubs/noauth/app/Http/Controllers/PageController.ts.stub +41 -0
  68. package/stubs/noauth/app/Models/User.ts.stub +5 -0
  69. package/stubs/noauth/database/migrations/001_create_users_table.ts.stub +17 -0
  70. package/stubs/noauth/routes/api.ts.stub +16 -0
  71. package/stubs/noauth/routes/web.ts.stub +15 -0
  72. package/stubs/noauth/svelte/src/App.svelte.stub +68 -0
  73. package/stubs/noauth/svelte/src/pages.ts.stub +7 -0
  74. package/stubs/noauth/vue/src/App.vue.stub +62 -0
  75. package/stubs/noauth/vue/src/pages.ts.stub +7 -0
  76. package/stubs/react/src/App.tsx.stub +4 -2
  77. package/stubs/react/src/components/layout/search-dialog.tsx.stub +2 -2
  78. package/stubs/react/src/components/layout/sidebar-data.ts.stub +2 -2
  79. package/stubs/react/src/lib/api.ts.stub +30 -6
  80. package/stubs/react/src/pages/Login.tsx.stub +3 -3
  81. package/stubs/react/src/pages/users/dialogs.tsx.stub +7 -26
  82. package/stubs/react/vite.config.ts.stub +26 -3
  83. package/stubs/shared/app/Http/Controllers/ApiAuthController.ts.stub +57 -0
  84. package/stubs/shared/app/Http/Controllers/AuthController.ts.stub +14 -38
  85. package/stubs/shared/app/Http/Controllers/PageController.ts.stub +3 -3
  86. package/stubs/shared/app/Http/Controllers/UserController.ts.stub +61 -0
  87. package/stubs/shared/app/Http/Requests/LoginRequest.ts.stub +10 -0
  88. package/stubs/shared/app/Http/Requests/RegisterRequest.ts.stub +11 -0
  89. package/stubs/shared/app/Http/Requests/StoreUserRequest.ts.stub +11 -0
  90. package/stubs/shared/app/Http/Requests/UpdateUserRequest.ts.stub +11 -0
  91. package/stubs/shared/config/app.ts.stub +36 -0
  92. package/stubs/shared/config/vite.ts.stub +8 -0
  93. package/stubs/shared/database/factories/UserFactory.ts.stub +4 -6
  94. package/stubs/shared/routes/api.ts.stub +12 -102
  95. package/stubs/shared/routes/web.ts.stub +5 -3
  96. package/stubs/shared/tests/feature/auth.test.ts.stub +69 -0
  97. package/stubs/shared/tests/feature/users.test.ts.stub +90 -0
  98. package/stubs/svelte/src/App.svelte.stub +1 -1
  99. package/stubs/svelte/src/lib/api.ts.stub +30 -6
  100. package/stubs/svelte/src/main.ts.stub +3 -1
  101. package/stubs/svelte/src/pages/Login.svelte.stub +3 -3
  102. package/stubs/svelte/vite.config.ts.stub +20 -1
  103. package/stubs/tailwind-only/react/src/components/layout/app-sidebar.tsx.stub +68 -0
  104. package/stubs/tailwind-only/react/src/components/layout/authenticated-layout.tsx.stub +57 -0
  105. package/stubs/tailwind-only/react/src/components/layout/header.tsx.stub +52 -0
  106. package/stubs/tailwind-only/react/src/components/layout/main.tsx.stub +21 -0
  107. package/stubs/tailwind-only/react/src/components/layout/nav-group.tsx.stub +185 -0
  108. package/stubs/tailwind-only/react/src/components/layout/nav-user.tsx.stub +106 -0
  109. package/stubs/tailwind-only/react/src/components/layout/sidebar-data.ts.stub +58 -0
  110. package/stubs/tailwind-only/react/src/components/layout/theme-toggle.tsx.stub +36 -0
  111. package/stubs/tailwind-only/react/src/components/layout/top-nav.tsx.stub +72 -0
  112. package/stubs/tailwind-only/react/src/lib/utils.ts.stub +6 -0
  113. package/stubs/tailwind-only/react/src/pages/Dashboard.tsx.stub +205 -0
  114. package/stubs/tailwind-only/react/src/pages/Login.tsx.stub +122 -0
  115. package/stubs/tailwind-only/react/src/pages/Register.tsx.stub +137 -0
  116. package/stubs/tailwind-only/react/src/pages/Users.tsx.stub +426 -0
  117. package/stubs/tailwind-only/react/src/pages/account/layout.tsx.stub +64 -0
  118. package/stubs/tailwind-only/react/src/pages/account/preferences.tsx.stub +80 -0
  119. package/stubs/tailwind-only/react/src/pages/account/profile.tsx.stub +67 -0
  120. package/stubs/tailwind-only/react/src/pages/account/security.tsx.stub +91 -0
  121. package/stubs/tailwind-only/react/src/style.css.stub +14 -0
  122. package/stubs/tailwind-only/svelte/src/lib/components/layout/app-sidebar.svelte.stub +104 -0
  123. package/stubs/tailwind-only/svelte/src/lib/components/layout/authenticated-layout.svelte.stub +51 -0
  124. package/stubs/tailwind-only/svelte/src/lib/components/layout/header.svelte.stub +66 -0
  125. package/stubs/tailwind-only/svelte/src/lib/components/layout/main.svelte.stub +26 -0
  126. package/stubs/tailwind-only/svelte/src/lib/components/layout/nav-group.svelte.stub +131 -0
  127. package/stubs/tailwind-only/svelte/src/lib/components/layout/nav-user.svelte.stub +104 -0
  128. package/stubs/tailwind-only/svelte/src/lib/components/layout/sidebar-data.ts.stub +57 -0
  129. package/stubs/tailwind-only/svelte/src/lib/components/layout/theme-toggle.svelte.stub +28 -0
  130. package/stubs/tailwind-only/svelte/src/lib/components/layout/top-nav.svelte.stub +99 -0
  131. package/stubs/tailwind-only/svelte/src/lib/utils.ts.stub +6 -0
  132. package/stubs/tailwind-only/svelte/src/pages/Dashboard.svelte.stub +192 -0
  133. package/stubs/tailwind-only/svelte/src/pages/Login.svelte.stub +120 -0
  134. package/stubs/tailwind-only/svelte/src/pages/Register.svelte.stub +134 -0
  135. package/stubs/tailwind-only/svelte/src/pages/Users.svelte.stub +293 -0
  136. package/stubs/tailwind-only/svelte/src/pages/account/Layout.svelte.stub +61 -0
  137. package/stubs/tailwind-only/svelte/src/pages/account/Preferences.svelte.stub +81 -0
  138. package/stubs/tailwind-only/svelte/src/pages/account/Profile.svelte.stub +76 -0
  139. package/stubs/tailwind-only/svelte/src/pages/account/Security.svelte.stub +140 -0
  140. package/stubs/tailwind-only/svelte/src/style.css.stub +127 -0
  141. package/stubs/tailwind-only/vue/src/components/layout/AppSidebar.vue.stub +60 -0
  142. package/stubs/tailwind-only/vue/src/components/layout/AuthenticatedLayout.vue.stub +73 -0
  143. package/stubs/tailwind-only/vue/src/components/layout/Header.vue.stub +54 -0
  144. package/stubs/tailwind-only/vue/src/components/layout/Main.vue.stub +22 -0
  145. package/stubs/tailwind-only/vue/src/components/layout/NavGroup.vue.stub +107 -0
  146. package/stubs/tailwind-only/vue/src/components/layout/NavUser.vue.stub +104 -0
  147. package/stubs/tailwind-only/vue/src/components/layout/ThemeToggle.vue.stub +28 -0
  148. package/stubs/tailwind-only/vue/src/components/layout/TopNav.vue.stub +86 -0
  149. package/stubs/tailwind-only/vue/src/components/layout/sidebar-data.ts.stub +57 -0
  150. package/stubs/tailwind-only/vue/src/lib/utils.ts.stub +7 -0
  151. package/stubs/tailwind-only/vue/src/pages/Dashboard.vue.stub +195 -0
  152. package/stubs/tailwind-only/vue/src/pages/Login.vue.stub +121 -0
  153. package/stubs/tailwind-only/vue/src/pages/Register.vue.stub +137 -0
  154. package/stubs/tailwind-only/vue/src/pages/Users.vue.stub +401 -0
  155. package/stubs/tailwind-only/vue/src/pages/account/Layout.vue.stub +56 -0
  156. package/stubs/tailwind-only/vue/src/pages/account/Preferences.vue.stub +81 -0
  157. package/stubs/tailwind-only/vue/src/pages/account/Profile.vue.stub +69 -0
  158. package/stubs/tailwind-only/vue/src/pages/account/Security.vue.stub +85 -0
  159. package/stubs/tailwind-only/vue/src/style.css.stub +26 -0
  160. package/stubs/themes/corporate/react/src/components/layout/app-sidebar.tsx.stub +74 -0
  161. package/stubs/themes/corporate/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
  162. package/stubs/themes/corporate/react/src/pages/Dashboard.tsx.stub +310 -0
  163. package/stubs/themes/corporate/react/src/pages/Login.tsx.stub +122 -0
  164. package/stubs/themes/corporate/react/src/pages/Register.tsx.stub +144 -0
  165. package/stubs/themes/corporate/react/src/style.css.stub +135 -0
  166. package/stubs/themes/corporate/svelte/src/pages/Dashboard.svelte.stub +271 -0
  167. package/stubs/themes/corporate/svelte/src/pages/Login.svelte.stub +117 -0
  168. package/stubs/themes/corporate/svelte/src/pages/Register.svelte.stub +138 -0
  169. package/stubs/themes/corporate/svelte/src/style.css.stub +134 -0
  170. package/stubs/themes/corporate/vue/src/pages/Dashboard.vue.stub +325 -0
  171. package/stubs/themes/corporate/vue/src/pages/Login.vue.stub +118 -0
  172. package/stubs/themes/corporate/vue/src/pages/Register.vue.stub +139 -0
  173. package/stubs/themes/corporate/vue/src/style.css.stub +134 -0
  174. package/stubs/themes/minimal/react/src/components/layout/app-sidebar.tsx.stub +68 -0
  175. package/stubs/themes/minimal/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
  176. package/stubs/themes/minimal/react/src/pages/Dashboard.tsx.stub +166 -0
  177. package/stubs/themes/minimal/react/src/pages/Login.tsx.stub +95 -0
  178. package/stubs/themes/minimal/react/src/pages/Register.tsx.stub +73 -0
  179. package/stubs/themes/minimal/react/src/style.css.stub +142 -0
  180. package/stubs/themes/minimal/svelte/src/pages/Dashboard.svelte.stub +149 -0
  181. package/stubs/themes/minimal/svelte/src/pages/Login.svelte.stub +90 -0
  182. package/stubs/themes/minimal/svelte/src/pages/Register.svelte.stub +70 -0
  183. package/stubs/themes/minimal/svelte/src/style.css.stub +142 -0
  184. package/stubs/themes/minimal/vue/src/pages/Dashboard.vue.stub +163 -0
  185. package/stubs/themes/minimal/vue/src/pages/Login.vue.stub +91 -0
  186. package/stubs/themes/minimal/vue/src/pages/Register.vue.stub +73 -0
  187. package/stubs/themes/minimal/vue/src/style.css.stub +142 -0
  188. package/stubs/themes/starter/react/src/components/layout/app-sidebar.tsx.stub +74 -0
  189. package/stubs/themes/starter/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
  190. package/stubs/themes/starter/react/src/pages/Dashboard.tsx.stub +236 -0
  191. package/stubs/themes/starter/react/src/pages/Login.tsx.stub +131 -0
  192. package/stubs/themes/starter/react/src/pages/Register.tsx.stub +145 -0
  193. package/stubs/themes/starter/react/src/style.css.stub +141 -0
  194. package/stubs/themes/starter/svelte/src/pages/Dashboard.svelte.stub +212 -0
  195. package/stubs/themes/starter/svelte/src/pages/Login.svelte.stub +126 -0
  196. package/stubs/themes/starter/svelte/src/pages/Register.svelte.stub +139 -0
  197. package/stubs/themes/starter/svelte/src/style.css.stub +141 -0
  198. package/stubs/themes/starter/vue/src/pages/Dashboard.vue.stub +228 -0
  199. package/stubs/themes/starter/vue/src/pages/Login.vue.stub +127 -0
  200. package/stubs/themes/starter/vue/src/pages/Register.vue.stub +140 -0
  201. package/stubs/themes/starter/vue/src/style.css.stub +141 -0
  202. package/stubs/themes/workspace/react/src/components/layout/app-sidebar.tsx.stub +97 -0
  203. package/stubs/themes/workspace/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
  204. package/stubs/themes/workspace/react/src/pages/Dashboard.tsx.stub +304 -0
  205. package/stubs/themes/workspace/react/src/pages/Login.tsx.stub +131 -0
  206. package/stubs/themes/workspace/react/src/pages/Register.tsx.stub +82 -0
  207. package/stubs/themes/workspace/react/src/style.css.stub +138 -0
  208. package/stubs/themes/workspace/svelte/src/pages/Dashboard.svelte.stub +215 -0
  209. package/stubs/themes/workspace/svelte/src/pages/Login.svelte.stub +124 -0
  210. package/stubs/themes/workspace/svelte/src/pages/Register.svelte.stub +76 -0
  211. package/stubs/themes/workspace/svelte/src/style.css.stub +134 -0
  212. package/stubs/themes/workspace/vue/src/pages/Dashboard.vue.stub +220 -0
  213. package/stubs/themes/workspace/vue/src/pages/Login.vue.stub +128 -0
  214. package/stubs/themes/workspace/vue/src/pages/Register.vue.stub +80 -0
  215. package/stubs/themes/workspace/vue/src/style.css.stub +134 -0
  216. package/stubs/vue/src/App.vue.stub +2 -1
  217. package/stubs/vue/src/lib/api.ts.stub +30 -6
  218. package/stubs/vue/src/main.ts.stub +3 -1
  219. package/stubs/vue/src/pages/Login.vue.stub +3 -3
  220. package/stubs/vue/vite.config.ts.stub +20 -1
@@ -0,0 +1,127 @@
1
+ @import "tailwindcss";
2
+ @custom-variant dark (&:where(.dark, .dark *));
3
+
4
+ @theme inline {
5
+ --color-background: var(--background);
6
+ --color-foreground: var(--foreground);
7
+ --color-card: var(--card);
8
+ --color-card-foreground: var(--card-foreground);
9
+ --color-popover: var(--popover);
10
+ --color-popover-foreground: var(--popover-foreground);
11
+ --color-primary: var(--primary);
12
+ --color-primary-foreground: var(--primary-foreground);
13
+ --color-secondary: var(--secondary);
14
+ --color-secondary-foreground: var(--secondary-foreground);
15
+ --color-muted: var(--muted);
16
+ --color-muted-foreground: var(--muted-foreground);
17
+ --color-accent: var(--accent);
18
+ --color-accent-foreground: var(--accent-foreground);
19
+ --color-destructive: var(--destructive);
20
+ --color-destructive-foreground: var(--destructive-foreground);
21
+ --color-border: var(--border);
22
+ --color-input: var(--input);
23
+ --color-ring: var(--ring);
24
+ --color-chart-1: var(--chart-1);
25
+ --color-chart-2: var(--chart-2);
26
+ --color-chart-3: var(--chart-3);
27
+ --color-chart-4: var(--chart-4);
28
+ --color-chart-5: var(--chart-5);
29
+ --radius-sm: calc(var(--radius) - 4px);
30
+ --radius-md: calc(var(--radius) - 2px);
31
+ --radius-lg: var(--radius);
32
+ --radius-xl: calc(var(--radius) + 4px);
33
+ --color-sidebar: var(--sidebar);
34
+ --color-sidebar-foreground: var(--sidebar-foreground);
35
+ --color-sidebar-primary: var(--sidebar-primary);
36
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
37
+ --color-sidebar-accent: var(--sidebar-accent);
38
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
39
+ --color-sidebar-border: var(--sidebar-border);
40
+ --color-sidebar-ring: var(--sidebar-ring);
41
+ }
42
+
43
+ :root {
44
+ --background: oklch(1 0 0);
45
+ --foreground: oklch(0.145 0 0);
46
+ --card: oklch(1 0 0);
47
+ --card-foreground: oklch(0.145 0 0);
48
+ --popover: oklch(1 0 0);
49
+ --popover-foreground: oklch(0.145 0 0);
50
+ --primary: oklch(0.205 0 0);
51
+ --primary-foreground: oklch(0.985 0 0);
52
+ --secondary: oklch(0.97 0 0);
53
+ --secondary-foreground: oklch(0.205 0 0);
54
+ --muted: oklch(0.97 0 0);
55
+ --muted-foreground: oklch(0.556 0 0);
56
+ --accent: oklch(0.97 0 0);
57
+ --accent-foreground: oklch(0.205 0 0);
58
+ --destructive: oklch(0.577 0.245 27.325);
59
+ --destructive-foreground: oklch(0.577 0.245 27.325);
60
+ --border: oklch(0.922 0 0);
61
+ --input: oklch(0.922 0 0);
62
+ --ring: oklch(0.708 0 0);
63
+ --chart-1: oklch(0.646 0.222 41.116);
64
+ --chart-2: oklch(0.6 0.118 184.704);
65
+ --chart-3: oklch(0.398 0.07 227.392);
66
+ --chart-4: oklch(0.828 0.189 84.429);
67
+ --chart-5: oklch(0.769 0.188 70.067);
68
+ --radius: 0.625rem;
69
+ --sidebar: oklch(0.985 0 0);
70
+ --sidebar-foreground: oklch(0.145 0 0);
71
+ --sidebar-primary: oklch(0.205 0 0);
72
+ --sidebar-primary-foreground: oklch(0.985 0 0);
73
+ --sidebar-accent: oklch(0.97 0 0);
74
+ --sidebar-accent-foreground: oklch(0.205 0 0);
75
+ --sidebar-border: oklch(0.922 0 0);
76
+ --sidebar-ring: oklch(0.708 0 0);
77
+ }
78
+
79
+ .dark {
80
+ --background: oklch(0.145 0 0);
81
+ --foreground: oklch(0.985 0 0);
82
+ --card: oklch(0.145 0 0);
83
+ --card-foreground: oklch(0.985 0 0);
84
+ --popover: oklch(0.145 0 0);
85
+ --popover-foreground: oklch(0.985 0 0);
86
+ --primary: oklch(0.985 0 0);
87
+ --primary-foreground: oklch(0.205 0 0);
88
+ --secondary: oklch(0.269 0 0);
89
+ --secondary-foreground: oklch(0.985 0 0);
90
+ --muted: oklch(0.269 0 0);
91
+ --muted-foreground: oklch(0.708 0 0);
92
+ --accent: oklch(0.269 0 0);
93
+ --accent-foreground: oklch(0.985 0 0);
94
+ --destructive: oklch(0.396 0.141 25.723);
95
+ --destructive-foreground: oklch(0.637 0.237 25.331);
96
+ --border: oklch(0.269 0 0);
97
+ --input: oklch(0.269 0 0);
98
+ --ring: oklch(0.439 0 0);
99
+ --chart-1: oklch(0.488 0.243 264.376);
100
+ --chart-2: oklch(0.696 0.17 162.48);
101
+ --chart-3: oklch(0.769 0.188 70.067);
102
+ --chart-4: oklch(0.627 0.265 303.9);
103
+ --chart-5: oklch(0.645 0.246 16.439);
104
+ --sidebar: oklch(0.205 0 0);
105
+ --sidebar-foreground: oklch(0.985 0 0);
106
+ --sidebar-primary: oklch(0.488 0.243 264.376);
107
+ --sidebar-primary-foreground: oklch(0.985 0 0);
108
+ --sidebar-accent: oklch(0.269 0 0);
109
+ --sidebar-accent-foreground: oklch(0.985 0 0);
110
+ --sidebar-border: oklch(0.269 0 0);
111
+ --sidebar-ring: oklch(0.439 0 0);
112
+ }
113
+
114
+ @layer base {
115
+ * {
116
+ @apply border-border;
117
+ }
118
+ body {
119
+ @apply bg-background text-foreground;
120
+ }
121
+ }
122
+
123
+ @keyframes fadeUp {
124
+ from { opacity: 0; transform: translateY(8px); }
125
+ to { opacity: 1; transform: translateY(0); }
126
+ }
127
+ .animate-fade-up { animation: fadeUp 0.4s ease-out; }
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ import { Command } from 'lucide-vue-next'
3
+ import NavGroup from './NavGroup.vue'
4
+ import NavUser from './NavUser.vue'
5
+ import type { NavUserUser } from './NavUser.vue'
6
+ import { sidebarData } from './sidebar-data'
7
+
8
+ const props = defineProps<{
9
+ user: NavUserUser
10
+ appName: string
11
+ activePath: string
12
+ navigate: (href: string) => void
13
+ onLogout: () => void
14
+ collapsed?: boolean
15
+ }>()
16
+ </script>
17
+
18
+ <template>
19
+ <aside
20
+ class="flex h-full flex-col border-r border-sidebar-border bg-sidebar text-sidebar-foreground transition-[width] duration-200"
21
+ :class="collapsed ? 'w-14' : 'w-64'"
22
+ >
23
+ <!-- Header -->
24
+ <div class="flex h-14 items-center gap-2 border-b border-sidebar-border px-3">
25
+ <button
26
+ class="flex items-center gap-2 rounded-md p-1.5 hover:bg-sidebar-accent"
27
+ :title="appName"
28
+ @click="navigate('/dashboard')"
29
+ >
30
+ <span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
31
+ <Command class="h-4 w-4" />
32
+ </span>
33
+ <template v-if="!collapsed">
34
+ <span class="grid flex-1 text-left text-sm leading-tight">
35
+ <span class="truncate font-semibold">{{ appName }}</span>
36
+ <span class="truncate text-xs text-muted-foreground">Admin Panel</span>
37
+ </span>
38
+ </template>
39
+ </button>
40
+ </div>
41
+
42
+ <!-- Content -->
43
+ <nav class="flex-1 overflow-y-auto py-2">
44
+ <NavGroup
45
+ v-for="group in sidebarData"
46
+ :key="group.title"
47
+ :group="group"
48
+ :active-path="activePath"
49
+ :navigate="navigate"
50
+ :collapsed="collapsed"
51
+ class="mb-2"
52
+ />
53
+ </nav>
54
+
55
+ <!-- Footer -->
56
+ <div class="border-t border-sidebar-border p-2">
57
+ <NavUser :user="user" :navigate="navigate" :on-logout="onLogout" :collapsed="collapsed" />
58
+ </div>
59
+ </aside>
60
+ </template>
@@ -0,0 +1,73 @@
1
+ <script setup lang="ts">
2
+ import { ref, provide, onMounted, onUnmounted } from 'vue'
3
+ import { post } from '@/lib/api'
4
+ import AppSidebar from './AppSidebar.vue'
5
+
6
+ const props = withDefaults(defineProps<{
7
+ currentUser?: { name: string; email: string; role?: string } | null
8
+ appName?: string
9
+ navigate: (href: string) => void
10
+ activePath: string
11
+ }>(), {
12
+ appName: 'Mantiq',
13
+ })
14
+
15
+ const mobileOpen = ref(false)
16
+
17
+ function toggleMobileSidebar() {
18
+ mobileOpen.value = !mobileOpen.value
19
+ }
20
+
21
+ provide('toggleMobileSidebar', toggleMobileSidebar)
22
+
23
+ async function handleLogout() {
24
+ await post('/logout', {})
25
+ props.navigate('/login')
26
+ }
27
+
28
+ const user = props.currentUser ?? { name: 'User', email: 'user@example.com' }
29
+
30
+ function onClickOutside(e: MouseEvent) {
31
+ const target = e.target as HTMLElement
32
+ if (mobileOpen.value && !target.closest('[data-sidebar]')) {
33
+ mobileOpen.value = false
34
+ }
35
+ }
36
+
37
+ onMounted(() => document.addEventListener('click', onClickOutside))
38
+ onUnmounted(() => document.removeEventListener('click', onClickOutside))
39
+ </script>
40
+
41
+ <template>
42
+ <div class="flex min-h-screen">
43
+ <!-- Desktop sidebar -->
44
+ <div class="hidden md:flex" data-sidebar>
45
+ <AppSidebar
46
+ :user="user"
47
+ :app-name="appName"
48
+ :active-path="activePath"
49
+ :navigate="navigate"
50
+ :on-logout="handleLogout"
51
+ />
52
+ </div>
53
+
54
+ <!-- Mobile sidebar overlay -->
55
+ <div v-if="mobileOpen" class="fixed inset-0 z-40 md:hidden">
56
+ <div class="fixed inset-0 bg-black/50" @click="mobileOpen = false" />
57
+ <div class="fixed inset-y-0 left-0 z-50 w-64" data-sidebar @click.stop>
58
+ <AppSidebar
59
+ :user="user"
60
+ :app-name="appName"
61
+ :active-path="activePath"
62
+ :navigate="(href: string) => { mobileOpen = false; navigate(href) }"
63
+ :on-logout="handleLogout"
64
+ />
65
+ </div>
66
+ </div>
67
+
68
+ <!-- Main content -->
69
+ <div class="flex flex-1 flex-col">
70
+ <slot />
71
+ </div>
72
+ </div>
73
+ </template>
@@ -0,0 +1,54 @@
1
+ <script setup lang="ts">
2
+ import { ref, inject, onMounted, onUnmounted } from 'vue'
3
+ import { cn } from '@/lib/utils'
4
+ import { PanelLeft } from 'lucide-vue-next'
5
+ import ThemeToggle from './ThemeToggle.vue'
6
+
7
+ const props = defineProps<{
8
+ fixed?: boolean
9
+ class?: string
10
+ navigate?: (href: string) => void
11
+ }>()
12
+
13
+ const toggleMobileSidebar = inject<() => void>('toggleMobileSidebar', () => {})
14
+
15
+ const offset = ref(0)
16
+
17
+ function onScroll() {
18
+ offset.value = document.body.scrollTop || document.documentElement.scrollTop
19
+ }
20
+
21
+ onMounted(() => {
22
+ document.addEventListener('scroll', onScroll, { passive: true })
23
+ })
24
+
25
+ onUnmounted(() => {
26
+ document.removeEventListener('scroll', onScroll)
27
+ })
28
+ </script>
29
+
30
+ <template>
31
+ <header
32
+ :class="cn(
33
+ 'flex h-16 shrink-0 items-center gap-2',
34
+ fixed && 'sticky top-0 z-10 bg-background',
35
+ offset > 10 && fixed ? 'border-b' : '',
36
+ props.class,
37
+ )"
38
+ >
39
+ <div class="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
40
+ <button
41
+ class="-ml-1 inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground md:hidden"
42
+ @click="toggleMobileSidebar"
43
+ >
44
+ <PanelLeft class="h-4 w-4" />
45
+ <span class="sr-only">Toggle sidebar</span>
46
+ </button>
47
+ <div class="mx-1 hidden h-4 w-px bg-border md:block" />
48
+ <slot />
49
+ <div class="ms-auto flex items-center gap-2">
50
+ <ThemeToggle />
51
+ </div>
52
+ </div>
53
+ </header>
54
+ </template>
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ import { cn } from '@/lib/utils'
3
+
4
+ const props = defineProps<{
5
+ fixed?: boolean
6
+ class?: string
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <main
12
+ :class="cn(
13
+ 'peer-[.header-fixed]/header:mt-16',
14
+ fixed && 'flex flex-grow flex-col overflow-hidden',
15
+ props.class,
16
+ )"
17
+ >
18
+ <div class="px-4 py-6 lg:px-6">
19
+ <slot />
20
+ </div>
21
+ </main>
22
+ </template>
@@ -0,0 +1,107 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { ChevronRight, ExternalLink } from 'lucide-vue-next'
4
+ import type { NavGroup as NavGroupData } from './sidebar-data'
5
+
6
+ const props = defineProps<{
7
+ group: NavGroupData
8
+ activePath: string
9
+ navigate: (href: string) => void
10
+ collapsed?: boolean
11
+ }>()
12
+
13
+ const expandedItems = ref<Record<string, boolean>>({})
14
+
15
+ function isActive(itemUrl: string, activePath: string): boolean {
16
+ if (itemUrl === activePath) return true
17
+ const itemBase = itemUrl.split('?')[0]
18
+ const activeBase = activePath.split('?')[0]
19
+ return itemBase === activeBase
20
+ }
21
+
22
+ function isGroupActive(items: NavGroupData['items'][number]['items'], activePath: string): boolean {
23
+ if (!items) return false
24
+ return items.some((sub) => isActive(sub.url, activePath))
25
+ }
26
+
27
+ function toggleExpanded(title: string) {
28
+ expandedItems.value[title] = !expandedItems.value[title]
29
+ }
30
+
31
+ function isExpanded(item: NavGroupData['items'][number]): boolean {
32
+ if (expandedItems.value[item.title] !== undefined) return expandedItems.value[item.title]
33
+ return isGroupActive(item.items, props.activePath)
34
+ }
35
+ </script>
36
+
37
+ <template>
38
+ <div class="space-y-1 px-2">
39
+ <p v-if="!collapsed" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
40
+ {{ group.title }}
41
+ </p>
42
+ <template v-for="item in group.items" :key="item.title">
43
+ <!-- Items with sub-items: collapsible -->
44
+ <template v-if="item.items && item.items.length > 0">
45
+ <div>
46
+ <button
47
+ class="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm font-medium transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
48
+ :class="isGroupActive(item.items, activePath) ? 'bg-sidebar-accent text-sidebar-accent-foreground' : 'text-sidebar-foreground'"
49
+ :title="item.title"
50
+ @click="toggleExpanded(item.title)"
51
+ >
52
+ <component :is="item.icon" class="h-4 w-4 shrink-0" />
53
+ <span v-if="!collapsed" class="flex-1 truncate text-left">{{ item.title }}</span>
54
+ <ChevronRight
55
+ v-if="!collapsed"
56
+ class="h-4 w-4 shrink-0 transition-transform duration-200"
57
+ :class="isExpanded(item) && 'rotate-90'"
58
+ />
59
+ </button>
60
+ <div v-if="!collapsed && isExpanded(item)" class="ml-4 mt-0.5 space-y-0.5 border-l border-sidebar-border pl-2">
61
+ <button
62
+ v-for="sub in item.items"
63
+ :key="sub.title"
64
+ class="flex w-full items-center rounded-md px-2 py-1 text-sm transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
65
+ :class="isActive(sub.url, activePath) ? 'bg-sidebar-accent text-sidebar-accent-foreground font-medium' : 'text-muted-foreground'"
66
+ @click.prevent="navigate(sub.url)"
67
+ >
68
+ {{ sub.title }}
69
+ </button>
70
+ </div>
71
+ </div>
72
+ </template>
73
+
74
+ <!-- External link -->
75
+ <a
76
+ v-else-if="item.external"
77
+ :href="item.url"
78
+ target="_blank"
79
+ rel="noopener noreferrer"
80
+ class="flex items-center gap-2 rounded-md px-2 py-1.5 text-sm font-medium text-sidebar-foreground transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
81
+ :title="item.title"
82
+ >
83
+ <component :is="item.icon" class="h-4 w-4 shrink-0" />
84
+ <span v-if="!collapsed" class="flex-1 truncate">{{ item.title }}</span>
85
+ <ExternalLink v-if="!collapsed" class="ml-auto h-3 w-3 text-muted-foreground" />
86
+ </a>
87
+
88
+ <!-- Regular item -->
89
+ <button
90
+ v-else
91
+ class="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm font-medium transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
92
+ :class="isActive(item.url, activePath) ? 'bg-sidebar-accent text-sidebar-accent-foreground' : 'text-sidebar-foreground'"
93
+ :title="item.title"
94
+ @click.prevent="navigate(item.url)"
95
+ >
96
+ <component :is="item.icon" class="h-4 w-4 shrink-0" />
97
+ <span v-if="!collapsed" class="flex-1 truncate text-left">{{ item.title }}</span>
98
+ <span
99
+ v-if="item.badge && !collapsed"
100
+ class="ml-auto rounded-full bg-secondary px-1.5 py-0 text-[10px] font-medium text-secondary-foreground"
101
+ >
102
+ {{ item.badge }}
103
+ </span>
104
+ </button>
105
+ </template>
106
+ </div>
107
+ </template>
@@ -0,0 +1,104 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted, onUnmounted } from 'vue'
3
+ import {
4
+ ChevronsUpDown,
5
+ LogOut,
6
+ User,
7
+ Settings,
8
+ } from 'lucide-vue-next'
9
+
10
+ export interface NavUserUser {
11
+ name: string
12
+ email: string
13
+ role?: string
14
+ }
15
+
16
+ const props = defineProps<{
17
+ user: NavUserUser
18
+ navigate: (href: string) => void
19
+ onLogout: () => void
20
+ collapsed?: boolean
21
+ }>()
22
+
23
+ const menuOpen = ref(false)
24
+
25
+ function getInitials(name: string) {
26
+ return name
27
+ .split(' ')
28
+ .map((n) => n[0])
29
+ .join('')
30
+ .toUpperCase()
31
+ .slice(0, 2)
32
+ }
33
+
34
+ function onClickOutside(e: MouseEvent) {
35
+ const target = e.target as HTMLElement
36
+ if (!target.closest('[data-nav-user]')) {
37
+ menuOpen.value = false
38
+ }
39
+ }
40
+
41
+ onMounted(() => document.addEventListener('click', onClickOutside))
42
+ onUnmounted(() => document.removeEventListener('click', onClickOutside))
43
+ </script>
44
+
45
+ <template>
46
+ <div class="relative" data-nav-user>
47
+ <button
48
+ class="flex w-full items-center gap-2 rounded-md p-2 text-left text-sm hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
49
+ @click.stop="menuOpen = !menuOpen"
50
+ >
51
+ <span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-muted text-xs font-semibold">
52
+ {{ getInitials(user.name) }}
53
+ </span>
54
+ <template v-if="!collapsed">
55
+ <span class="grid flex-1 text-left text-sm leading-tight">
56
+ <span class="truncate font-semibold">{{ user.name }}</span>
57
+ <span class="truncate text-xs text-muted-foreground">{{ user.email }}</span>
58
+ </span>
59
+ <ChevronsUpDown class="ml-auto h-4 w-4 text-muted-foreground" />
60
+ </template>
61
+ </button>
62
+
63
+ <div
64
+ v-if="menuOpen"
65
+ class="absolute bottom-full left-0 z-50 mb-1 min-w-[14rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md"
66
+ >
67
+ <!-- User info -->
68
+ <div class="px-2 py-1.5">
69
+ <div class="flex items-center gap-2">
70
+ <span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-muted text-xs font-semibold">
71
+ {{ getInitials(user.name) }}
72
+ </span>
73
+ <span class="grid flex-1 text-left text-sm leading-tight">
74
+ <span class="truncate font-semibold">{{ user.name }}</span>
75
+ <span class="truncate text-xs text-muted-foreground">{{ user.email }}</span>
76
+ </span>
77
+ </div>
78
+ </div>
79
+ <div class="my-1 h-px bg-border" />
80
+ <button
81
+ class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground"
82
+ @click="menuOpen = false; navigate('/account/profile')"
83
+ >
84
+ <User class="h-4 w-4" />
85
+ Account
86
+ </button>
87
+ <button
88
+ class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground"
89
+ @click="menuOpen = false; navigate('/account/preferences')"
90
+ >
91
+ <Settings class="h-4 w-4" />
92
+ Settings
93
+ </button>
94
+ <div class="my-1 h-px bg-border" />
95
+ <button
96
+ class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-destructive outline-none hover:bg-destructive/10"
97
+ @click="menuOpen = false; onLogout()"
98
+ >
99
+ <LogOut class="h-4 w-4" />
100
+ Sign out
101
+ </button>
102
+ </div>
103
+ </div>
104
+ </template>
@@ -0,0 +1,28 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { Sun, Moon } from 'lucide-vue-next'
4
+
5
+ const isDark = ref(
6
+ typeof document !== 'undefined'
7
+ ? document.documentElement.classList.contains('dark')
8
+ : false,
9
+ )
10
+
11
+ function toggleTheme() {
12
+ const dark = document.documentElement.classList.toggle('dark')
13
+ localStorage.setItem('theme', dark ? 'dark' : 'light')
14
+ isDark.value = dark
15
+ }
16
+ </script>
17
+
18
+ <template>
19
+ <button
20
+ class="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground"
21
+ title="Toggle theme"
22
+ @click="toggleTheme"
23
+ >
24
+ <Sun v-if="isDark" class="h-4 w-4" />
25
+ <Moon v-else class="h-4 w-4" />
26
+ <span class="sr-only">Toggle theme</span>
27
+ </button>
28
+ </template>
@@ -0,0 +1,86 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted, onUnmounted } from 'vue'
3
+ import { cn } from '@/lib/utils'
4
+ import { Menu } from 'lucide-vue-next'
5
+
6
+ export interface TopNavLink {
7
+ title: string
8
+ href: string
9
+ isActive?: boolean
10
+ disabled?: boolean
11
+ }
12
+
13
+ const props = defineProps<{
14
+ links: TopNavLink[]
15
+ class?: string
16
+ }>()
17
+
18
+ const emit = defineEmits<{
19
+ linkClick: [href: string]
20
+ }>()
21
+
22
+ const mobileOpen = ref(false)
23
+
24
+ function handleClick(e: MouseEvent, href: string) {
25
+ e.preventDefault()
26
+ emit('linkClick', href)
27
+ }
28
+
29
+ function onClickOutside(e: MouseEvent) {
30
+ const target = e.target as HTMLElement
31
+ if (!target.closest('[data-top-nav-mobile]')) {
32
+ mobileOpen.value = false
33
+ }
34
+ }
35
+
36
+ onMounted(() => document.addEventListener('click', onClickOutside))
37
+ onUnmounted(() => document.removeEventListener('click', onClickOutside))
38
+ </script>
39
+
40
+ <template>
41
+ <!-- Desktop navigation -->
42
+ <nav
43
+ :class="cn('hidden items-center gap-4 md:flex lg:gap-6', props.class)"
44
+ >
45
+ <a
46
+ v-for="link in links"
47
+ :key="link.href"
48
+ :href="link.href"
49
+ :class="cn(
50
+ 'text-sm font-medium transition-colors hover:text-primary',
51
+ link.isActive ? 'text-foreground' : 'text-muted-foreground',
52
+ link.disabled && 'pointer-events-none opacity-50',
53
+ )"
54
+ @click="handleClick($event, link.href)"
55
+ >
56
+ {{ link.title }}
57
+ </a>
58
+ </nav>
59
+
60
+ <!-- Mobile navigation -->
61
+ <div class="relative md:hidden" data-top-nav-mobile>
62
+ <button
63
+ class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-input bg-background text-muted-foreground hover:bg-accent hover:text-accent-foreground"
64
+ @click.stop="mobileOpen = !mobileOpen"
65
+ >
66
+ <Menu class="h-4 w-4" />
67
+ <span class="sr-only">Toggle navigation</span>
68
+ </button>
69
+ <div
70
+ v-if="mobileOpen"
71
+ class="absolute left-0 top-full z-50 mt-1 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md"
72
+ >
73
+ <button
74
+ v-for="link in links"
75
+ :key="link.href"
76
+ :disabled="link.disabled"
77
+ class="flex w-full items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"
78
+ @click="mobileOpen = false; emit('linkClick', link.href)"
79
+ >
80
+ <span :class="cn(!link.isActive && 'text-muted-foreground')">
81
+ {{ link.title }}
82
+ </span>
83
+ </button>
84
+ </div>
85
+ </div>
86
+ </template>