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,138 @@
1
+ <script lang="ts">
2
+ import { post } from '$lib/api'
3
+ import { Button } from '$lib/components/ui/button'
4
+ import { Input } from '$lib/components/ui/input'
5
+ import { Label } from '$lib/components/ui/label'
6
+ import { Separator } from '$lib/components/ui/separator'
7
+
8
+ interface Props {
9
+ appName?: string
10
+ navigate?: (href: string) => void
11
+ }
12
+ let { appName = 'Mantiq', navigate = () => {} }: Props = $props()
13
+
14
+ let name = $state('')
15
+ let email = $state('')
16
+ let password = $state('')
17
+ let error = $state('')
18
+ let loading = $state(false)
19
+
20
+ async function handleSubmit(e: SubmitEvent) {
21
+ e.preventDefault()
22
+ error = ''
23
+ loading = true
24
+ const { ok, data } = await post('/register', { name, email, password })
25
+ if (ok) navigate('/dashboard')
26
+ else error = data?.error?.message ?? data?.error ?? 'Registration failed. Please try again.'
27
+ loading = false
28
+ }
29
+ </script>
30
+
31
+ <div class="min-h-screen bg-muted/50 flex items-center justify-center p-4">
32
+ <div class="w-full max-w-sm">
33
+ <!-- Logo -->
34
+ <div class="mb-8 flex flex-col items-center gap-3">
35
+ <div class="flex h-10 w-10 items-center justify-center rounded-lg bg-primary text-primary-foreground text-sm font-bold shadow-sm">
36
+ M
37
+ </div>
38
+ <span class="text-lg font-semibold tracking-tight">{appName}</span>
39
+ </div>
40
+
41
+ <!-- Card -->
42
+ <div class="rounded-lg border bg-card shadow-sm p-6">
43
+ <div class="mb-6">
44
+ <h1 class="text-xl font-semibold tracking-tight">Create an account</h1>
45
+ <p class="mt-1 text-sm text-muted-foreground">
46
+ Get started with {appName} today
47
+ </p>
48
+ </div>
49
+
50
+ {#if error}
51
+ <div class="mb-4 rounded-md border border-destructive px-4 py-3 text-sm text-destructive">
52
+ {error}
53
+ </div>
54
+ {/if}
55
+
56
+ <form onsubmit={handleSubmit} class="space-y-4">
57
+ <div class="space-y-2">
58
+ <Label for="name">Name</Label>
59
+ <Input
60
+ id="name"
61
+ bind:value={name}
62
+ required
63
+ placeholder="John Doe"
64
+ autocomplete="name"
65
+ />
66
+ </div>
67
+ <div class="space-y-2">
68
+ <Label for="email">Email</Label>
69
+ <Input
70
+ id="email"
71
+ type="email"
72
+ bind:value={email}
73
+ required
74
+ placeholder="you@example.com"
75
+ autocomplete="email"
76
+ />
77
+ </div>
78
+ <div class="space-y-2">
79
+ <Label for="password">Password</Label>
80
+ <Input
81
+ id="password"
82
+ type="password"
83
+ bind:value={password}
84
+ required
85
+ placeholder="Create a strong password"
86
+ autocomplete="new-password"
87
+ minlength={8}
88
+ />
89
+ <p class="text-xs text-muted-foreground">Must be at least 8 characters</p>
90
+ </div>
91
+
92
+ <div class="flex items-start gap-2">
93
+ <input
94
+ type="checkbox"
95
+ id="terms"
96
+ class="mt-1 h-3.5 w-3.5 rounded border-input accent-primary"
97
+ required
98
+ />
99
+ <label for="terms" class="text-xs text-muted-foreground leading-relaxed">
100
+ I agree to the{' '}
101
+ <button type="button" class="font-medium text-primary hover:text-primary/80">Terms of Service</button>
102
+ {' '}and{' '}
103
+ <button type="button" class="font-medium text-primary hover:text-primary/80">Privacy Policy</button>
104
+ </label>
105
+ </div>
106
+
107
+ <Button type="submit" class="w-full" disabled={loading}>
108
+ {loading ? 'Creating account...' : 'Create account'}
109
+ </Button>
110
+ </form>
111
+
112
+ <div class="relative my-6">
113
+ <div class="absolute inset-0 flex items-center">
114
+ <Separator class="w-full" />
115
+ </div>
116
+ <div class="relative flex justify-center text-xs uppercase">
117
+ <span class="bg-card px-2 text-muted-foreground">or</span>
118
+ </div>
119
+ </div>
120
+
121
+ <p class="text-center text-sm text-muted-foreground">
122
+ Already have an account?{' '}
123
+ <button
124
+ type="button"
125
+ class="font-medium text-primary hover:text-primary/80"
126
+ onclick={() => navigate('/login')}
127
+ >
128
+ Sign in
129
+ </button>
130
+ </p>
131
+ </div>
132
+
133
+ <!-- Footer -->
134
+ <p class="mt-6 text-center text-xs text-muted-foreground">
135
+ Powered by {appName}
136
+ </p>
137
+ </div>
138
+ </div>
@@ -0,0 +1,134 @@
1
+ @import "tailwindcss";
2
+ @custom-variant dark (&:where(.dark, .dark *));
3
+
4
+ /*
5
+ * shadcn/ui theme — Corporate
6
+ *
7
+ * Clean zinc neutrals with indigo/violet accent.
8
+ * Inspired by Corporate and Tailwind's professional design language.
9
+ */
10
+
11
+ @theme inline {
12
+ --color-background: var(--background);
13
+ --color-foreground: var(--foreground);
14
+ --color-card: var(--card);
15
+ --color-card-foreground: var(--card-foreground);
16
+ --color-popover: var(--popover);
17
+ --color-popover-foreground: var(--popover-foreground);
18
+ --color-primary: var(--primary);
19
+ --color-primary-foreground: var(--primary-foreground);
20
+ --color-secondary: var(--secondary);
21
+ --color-secondary-foreground: var(--secondary-foreground);
22
+ --color-muted: var(--muted);
23
+ --color-muted-foreground: var(--muted-foreground);
24
+ --color-accent: var(--accent);
25
+ --color-accent-foreground: var(--accent-foreground);
26
+ --color-destructive: var(--destructive);
27
+ --color-destructive-foreground: var(--destructive-foreground);
28
+ --color-border: var(--border);
29
+ --color-input: var(--input);
30
+ --color-ring: var(--ring);
31
+ --color-chart-1: var(--chart-1);
32
+ --color-chart-2: var(--chart-2);
33
+ --color-chart-3: var(--chart-3);
34
+ --color-chart-4: var(--chart-4);
35
+ --color-chart-5: var(--chart-5);
36
+ --color-sidebar: var(--sidebar);
37
+ --color-sidebar-foreground: var(--sidebar-foreground);
38
+ --color-sidebar-primary: var(--sidebar-primary);
39
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
40
+ --color-sidebar-accent: var(--sidebar-accent);
41
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
42
+ --color-sidebar-border: var(--sidebar-border);
43
+ --color-sidebar-ring: var(--sidebar-ring);
44
+ --radius-sm: calc(var(--radius) - 4px);
45
+ --radius-md: calc(var(--radius) - 2px);
46
+ --radius-lg: var(--radius);
47
+ --radius-xl: calc(var(--radius) + 4px);
48
+ }
49
+
50
+ :root {
51
+ --radius: 0.625rem;
52
+ --background: oklch(0.985 0.002 247.858);
53
+ --foreground: oklch(0.141 0.005 285.823);
54
+ --card: oklch(1 0 0);
55
+ --card-foreground: oklch(0.141 0.005 285.823);
56
+ --popover: oklch(1 0 0);
57
+ --popover-foreground: oklch(0.141 0.005 285.823);
58
+ --primary: oklch(0.585 0.233 277.117);
59
+ --primary-foreground: oklch(0.985 0 0);
60
+ --secondary: oklch(0.967 0.001 286.375);
61
+ --secondary-foreground: oklch(0.21 0.006 285.885);
62
+ --muted: oklch(0.967 0.001 286.375);
63
+ --muted-foreground: oklch(0.552 0.016 285.938);
64
+ --accent: oklch(0.967 0.001 286.375);
65
+ --accent-foreground: oklch(0.21 0.006 285.885);
66
+ --destructive: oklch(0.577 0.245 27.325);
67
+ --destructive-foreground: oklch(0.577 0.245 27.325);
68
+ --border: oklch(0.922 0.004 286.32);
69
+ --input: oklch(0.922 0.004 286.32);
70
+ --ring: oklch(0.585 0.233 277.117);
71
+ --chart-1: oklch(0.585 0.233 277.117);
72
+ --chart-2: oklch(0.553 0.213 264.364);
73
+ --chart-3: oklch(0.496 0.265 301.924);
74
+ --chart-4: oklch(0.6 0.118 184.714);
75
+ --chart-5: oklch(0.828 0.189 84.429);
76
+ --sidebar: oklch(0.985 0.002 247.858);
77
+ --sidebar-foreground: oklch(0.141 0.005 285.823);
78
+ --sidebar-primary: oklch(0.585 0.233 277.117);
79
+ --sidebar-primary-foreground: oklch(0.985 0 0);
80
+ --sidebar-accent: oklch(0.967 0.001 286.375);
81
+ --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
82
+ --sidebar-border: oklch(0.922 0.004 286.32);
83
+ --sidebar-ring: oklch(0.585 0.233 277.117);
84
+ }
85
+
86
+ .dark {
87
+ --background: oklch(0.141 0.005 285.823);
88
+ --foreground: oklch(0.985 0 0);
89
+ --card: oklch(0.178 0.005 285.823);
90
+ --card-foreground: oklch(0.985 0 0);
91
+ --popover: oklch(0.178 0.005 285.823);
92
+ --popover-foreground: oklch(0.985 0 0);
93
+ --primary: oklch(0.65 0.21 277.117);
94
+ --primary-foreground: oklch(0.145 0.004 285.823);
95
+ --secondary: oklch(0.269 0.006 286.033);
96
+ --secondary-foreground: oklch(0.985 0 0);
97
+ --muted: oklch(0.269 0.006 286.033);
98
+ --muted-foreground: oklch(0.708 0.014 286.067);
99
+ --accent: oklch(0.269 0.006 286.033);
100
+ --accent-foreground: oklch(0.985 0 0);
101
+ --destructive: oklch(0.704 0.191 22.216);
102
+ --destructive-foreground: oklch(0.704 0.191 22.216);
103
+ --border: oklch(0.274 0.006 286.033);
104
+ --input: oklch(0.274 0.006 286.033);
105
+ --ring: oklch(0.65 0.21 277.117);
106
+ --chart-1: oklch(0.65 0.21 277.117);
107
+ --chart-2: oklch(0.623 0.214 262.881);
108
+ --chart-3: oklch(0.627 0.265 303.9);
109
+ --chart-4: oklch(0.696 0.17 162.48);
110
+ --chart-5: oklch(0.769 0.188 70.08);
111
+ --sidebar: oklch(0.16 0.005 285.823);
112
+ --sidebar-foreground: oklch(0.985 0 0);
113
+ --sidebar-primary: oklch(0.65 0.21 277.117);
114
+ --sidebar-primary-foreground: oklch(0.145 0.004 285.823);
115
+ --sidebar-accent: oklch(0.269 0.006 286.033);
116
+ --sidebar-accent-foreground: oklch(0.985 0 0);
117
+ --sidebar-border: oklch(0.274 0.006 286.033);
118
+ --sidebar-ring: oklch(0.65 0.21 277.117);
119
+ }
120
+
121
+ @layer base {
122
+ * {
123
+ @apply border-border;
124
+ }
125
+ body {
126
+ @apply bg-background text-foreground;
127
+ }
128
+ }
129
+
130
+ @keyframes fadeUp {
131
+ from { opacity: 0; transform: translateY(8px); }
132
+ to { opacity: 1; transform: translateY(0); }
133
+ }
134
+ .animate-fade-up { animation: fadeUp 0.4s ease-out; }
@@ -0,0 +1,325 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import AuthenticatedLayout from '@/components/layout/AuthenticatedLayout.vue'
4
+ import Header from '@/components/layout/Header.vue'
5
+ import Main from '@/components/layout/Main.vue'
6
+ import TopNav from '@/components/layout/TopNav.vue'
7
+ import { Button } from '@/components/ui/button'
8
+ import {
9
+ Card,
10
+ CardContent,
11
+ CardHeader,
12
+ CardTitle,
13
+ CardDescription,
14
+ } from '@/components/ui/card'
15
+ import { Badge } from '@/components/ui/badge'
16
+ import { Separator } from '@/components/ui/separator'
17
+ import {
18
+ Table,
19
+ TableBody,
20
+ TableCell,
21
+ TableHead,
22
+ TableHeader,
23
+ TableRow,
24
+ } from '@/components/ui/table'
25
+ import {
26
+ TrendingUp,
27
+ ArrowUpRight,
28
+ ArrowDownRight,
29
+ CreditCard,
30
+ DollarSign,
31
+ Users,
32
+ Activity,
33
+ } from 'lucide-vue-next'
34
+
35
+ const props = withDefaults(defineProps<{
36
+ appName?: string
37
+ currentUser?: { id: number; name: string; email: string; role: string } | null
38
+ navigate?: (href: string) => void
39
+ }>(), {
40
+ appName: 'Mantiq',
41
+ })
42
+
43
+ const topNav = [
44
+ { title: 'Overview', href: '/dashboard', isActive: true },
45
+ { title: 'Sales', href: '/dashboard', isActive: false, disabled: true },
46
+ { title: 'Tickets', href: '/dashboard', isActive: false, disabled: true },
47
+ { title: 'Performance', href: '/dashboard', isActive: false, disabled: true },
48
+ ]
49
+
50
+ const transactions = [
51
+ { customer: 'Olivia Martin', email: 'olivia@email.com', amount: '$1,999.00', status: 'Completed', date: 'Mar 15, 2025' },
52
+ { customer: 'Jackson Lee', email: 'jackson@email.com', amount: '$39.00', status: 'Completed', date: 'Mar 14, 2025' },
53
+ { customer: 'Isabella Nguyen', email: 'isabella@email.com', amount: '$299.00', status: 'Pending', date: 'Mar 14, 2025' },
54
+ { customer: 'William Kim', email: 'will@email.com', amount: '$99.00', status: 'Failed', date: 'Mar 13, 2025' },
55
+ { customer: 'Sofia Davis', email: 'sofia@email.com', amount: '$39.00', status: 'Completed', date: 'Mar 13, 2025' },
56
+ ]
57
+
58
+ function statusColor(status: string) {
59
+ if (status === 'Completed') return 'bg-emerald-500/15 text-emerald-700 dark:text-emerald-400 border-emerald-500/20'
60
+ if (status === 'Pending') return 'bg-amber-500/15 text-amber-700 dark:text-amber-400 border-amber-500/20'
61
+ return 'bg-red-500/15 text-red-700 dark:text-red-400 border-red-500/20'
62
+ }
63
+
64
+ const activeRange = ref('7d')
65
+
66
+ const ranges = ['7d', '30d', '90d'] as const
67
+
68
+ function rangeLabel(range: string) {
69
+ if (range === '7d') return '7 days'
70
+ if (range === '30d') return '30 days'
71
+ return '90 days'
72
+ }
73
+
74
+ // Revenue chart data
75
+ const chartData = [1200, 2100, 1800, 3200, 2800, 4100, 3600, 5200, 4800, 5800, 5100, 6200, 5600]
76
+ const maxVal = Math.max(...chartData)
77
+ const svgW = 700
78
+ const svgH = 220
79
+ const padL = 40
80
+ const padR = 10
81
+ const padT = 10
82
+ const padB = 30
83
+ const chartW = svgW - padL - padR
84
+ const chartH = svgH - padT - padB
85
+ const stepX = chartW / (chartData.length - 1)
86
+ const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
87
+ const yLabels = ['$0', '$2k', '$4k', '$6k']
88
+
89
+ const points = chartData.map((val, i) => ({
90
+ x: padL + i * stepX,
91
+ y: padT + chartH - (val / maxVal) * chartH,
92
+ }))
93
+
94
+ const linePath = points.map((p, i) => {
95
+ if (i === 0) return `M ${p.x} ${p.y}`
96
+ const prev = points[i - 1]
97
+ const cpx1 = prev.x + stepX * 0.4
98
+ const cpx2 = p.x - stepX * 0.4
99
+ return `C ${cpx1} ${prev.y}, ${cpx2} ${p.y}, ${p.x} ${p.y}`
100
+ }).join(' ')
101
+
102
+ const areaPath = `${linePath} L ${points[points.length - 1].x} ${padT + chartH} L ${points[0].x} ${padT + chartH} Z`
103
+
104
+ function yPos(index: number) {
105
+ return padT + chartH - (index / (yLabels.length - 1)) * chartH
106
+ }
107
+
108
+ function xLabelPos(index: number) {
109
+ const idx = Math.round((index / (days.length - 1)) * (chartData.length - 1))
110
+ return points[idx].x
111
+ }
112
+ </script>
113
+
114
+ <template>
115
+ <AuthenticatedLayout
116
+ :current-user="currentUser"
117
+ :app-name="appName"
118
+ :navigate="navigate"
119
+ active-path="/dashboard"
120
+ >
121
+ <Header :navigate="navigate">
122
+ <TopNav :links="topNav" :on-link-click="navigate" />
123
+ </Header>
124
+ <Main>
125
+ <div class="space-y-6">
126
+ <!-- Page title row -->
127
+ <div class="flex items-center justify-between">
128
+ <h2 class="text-3xl font-bold tracking-tight">Dashboard</h2>
129
+ <div class="flex items-center gap-1 rounded-lg border bg-card p-1">
130
+ <button
131
+ v-for="range in ranges"
132
+ :key="range"
133
+ :class="[
134
+ 'rounded-md px-3 py-1.5 text-xs font-medium transition-colors',
135
+ activeRange === range
136
+ ? 'bg-primary text-primary-foreground shadow-sm'
137
+ : 'text-muted-foreground hover:text-foreground',
138
+ ]"
139
+ @click="activeRange = range"
140
+ >
141
+ {{ rangeLabel(range) }}
142
+ </button>
143
+ </div>
144
+ </div>
145
+
146
+ <!-- Stat cards -->
147
+ <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
148
+ <Card>
149
+ <CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
150
+ <CardTitle class="text-sm font-medium">Total Revenue</CardTitle>
151
+ <DollarSign class="h-4 w-4 text-muted-foreground" />
152
+ </CardHeader>
153
+ <CardContent>
154
+ <div class="text-2xl font-bold">$45,231.89</div>
155
+ <div class="mt-1 flex items-center gap-1">
156
+ <Badge variant="outline" class="border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 gap-0.5 text-xs font-medium">
157
+ <ArrowUpRight class="h-3 w-3" />
158
+ +20.1%
159
+ </Badge>
160
+ <span class="text-xs text-muted-foreground">from last period</span>
161
+ </div>
162
+ </CardContent>
163
+ </Card>
164
+
165
+ <Card>
166
+ <CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
167
+ <CardTitle class="text-sm font-medium">New Customers</CardTitle>
168
+ <Users class="h-4 w-4 text-muted-foreground" />
169
+ </CardHeader>
170
+ <CardContent>
171
+ <div class="text-2xl font-bold">+2,350</div>
172
+ <div class="mt-1 flex items-center gap-1">
173
+ <Badge variant="outline" class="border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 gap-0.5 text-xs font-medium">
174
+ <ArrowUpRight class="h-3 w-3" />
175
+ +180.1%
176
+ </Badge>
177
+ <span class="text-xs text-muted-foreground">from last period</span>
178
+ </div>
179
+ </CardContent>
180
+ </Card>
181
+
182
+ <Card>
183
+ <CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
184
+ <CardTitle class="text-sm font-medium">Active Subscriptions</CardTitle>
185
+ <CreditCard class="h-4 w-4 text-muted-foreground" />
186
+ </CardHeader>
187
+ <CardContent>
188
+ <div class="text-2xl font-bold">+12,234</div>
189
+ <div class="mt-1 flex items-center gap-1">
190
+ <Badge variant="outline" class="border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 gap-0.5 text-xs font-medium">
191
+ <ArrowUpRight class="h-3 w-3" />
192
+ +19%
193
+ </Badge>
194
+ <span class="text-xs text-muted-foreground">from last period</span>
195
+ </div>
196
+ </CardContent>
197
+ </Card>
198
+
199
+ <Card>
200
+ <CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
201
+ <CardTitle class="text-sm font-medium">Churn Rate</CardTitle>
202
+ <Activity class="h-4 w-4 text-muted-foreground" />
203
+ </CardHeader>
204
+ <CardContent>
205
+ <div class="text-2xl font-bold">2.4%</div>
206
+ <div class="mt-1 flex items-center gap-1">
207
+ <Badge variant="outline" class="border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 gap-0.5 text-xs font-medium">
208
+ <ArrowDownRight class="h-3 w-3" />
209
+ -0.3%
210
+ </Badge>
211
+ <span class="text-xs text-muted-foreground">from last period</span>
212
+ </div>
213
+ </CardContent>
214
+ </Card>
215
+ </div>
216
+
217
+ <!-- Revenue chart -->
218
+ <Card>
219
+ <CardHeader>
220
+ <div class="flex items-center justify-between">
221
+ <div>
222
+ <CardTitle>Revenue Overview</CardTitle>
223
+ <CardDescription>Daily revenue for the selected period</CardDescription>
224
+ </div>
225
+ <div class="flex items-center gap-2 text-sm">
226
+ <TrendingUp class="h-4 w-4 text-emerald-600" />
227
+ <span class="font-medium text-emerald-600">+12.5%</span>
228
+ </div>
229
+ </div>
230
+ </CardHeader>
231
+ <CardContent>
232
+ <svg :viewBox="`0 0 ${svgW} ${svgH}`" class="h-[280px] w-full" preserveAspectRatio="xMidYMid meet">
233
+ <defs>
234
+ <linearGradient id="areaGradient" x1="0" y1="0" x2="0" y2="1">
235
+ <stop offset="0%" class="[stop-color:var(--color-primary)]" stop-opacity="0.3" />
236
+ <stop offset="100%" class="[stop-color:var(--color-primary)]" stop-opacity="0.02" />
237
+ </linearGradient>
238
+ </defs>
239
+
240
+ <!-- Grid lines -->
241
+ <line
242
+ v-for="(_, i) in yLabels"
243
+ :key="'grid-' + i"
244
+ :x1="padL"
245
+ :y1="yPos(i)"
246
+ :x2="svgW - padR"
247
+ :y2="yPos(i)"
248
+ class="stroke-border"
249
+ stroke-width="0.5"
250
+ stroke-dasharray="4 4"
251
+ />
252
+
253
+ <!-- Y-axis labels -->
254
+ <text
255
+ v-for="(label, i) in yLabels"
256
+ :key="'ylabel-' + i"
257
+ :x="padL - 6"
258
+ :y="yPos(i) + 3"
259
+ text-anchor="end"
260
+ class="fill-muted-foreground text-[9px]"
261
+ >
262
+ {{ label }}
263
+ </text>
264
+
265
+ <!-- Area fill -->
266
+ <path :d="areaPath" fill="url(#areaGradient)" />
267
+
268
+ <!-- Line -->
269
+ <path :d="linePath" fill="none" class="stroke-primary" stroke-width="2" stroke-linecap="round" />
270
+
271
+ <!-- X-axis labels -->
272
+ <text
273
+ v-for="(day, i) in days"
274
+ :key="'xlabel-' + i"
275
+ :x="xLabelPos(i)"
276
+ :y="svgH - 6"
277
+ text-anchor="middle"
278
+ class="fill-muted-foreground text-[9px]"
279
+ >
280
+ {{ day }}
281
+ </text>
282
+ </svg>
283
+ </CardContent>
284
+ </Card>
285
+
286
+ <!-- Recent Transactions -->
287
+ <Card>
288
+ <CardHeader>
289
+ <CardTitle>Recent Transactions</CardTitle>
290
+ <CardDescription>Latest payment activity across all channels</CardDescription>
291
+ </CardHeader>
292
+ <CardContent>
293
+ <Table>
294
+ <TableHeader>
295
+ <TableRow>
296
+ <TableHead>Customer</TableHead>
297
+ <TableHead>Amount</TableHead>
298
+ <TableHead>Status</TableHead>
299
+ <TableHead class="text-right">Date</TableHead>
300
+ </TableRow>
301
+ </TableHeader>
302
+ <TableBody>
303
+ <TableRow v-for="tx in transactions" :key="tx.email">
304
+ <TableCell>
305
+ <div>
306
+ <p class="font-medium">{{ tx.customer }}</p>
307
+ <p class="text-sm text-muted-foreground">{{ tx.email }}</p>
308
+ </div>
309
+ </TableCell>
310
+ <TableCell class="font-medium">{{ tx.amount }}</TableCell>
311
+ <TableCell>
312
+ <Badge variant="outline" :class="statusColor(tx.status)">
313
+ {{ tx.status }}
314
+ </Badge>
315
+ </TableCell>
316
+ <TableCell class="text-right text-muted-foreground">{{ tx.date }}</TableCell>
317
+ </TableRow>
318
+ </TableBody>
319
+ </Table>
320
+ </CardContent>
321
+ </Card>
322
+ </div>
323
+ </Main>
324
+ </AuthenticatedLayout>
325
+ </template>