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,118 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { post } from '@/lib/api'
4
+ import { Button } from '@/components/ui/button'
5
+ import { Input } from '@/components/ui/input'
6
+ import { Label } from '@/components/ui/label'
7
+ import { Separator } from '@/components/ui/separator'
8
+
9
+ const props = withDefaults(defineProps<{
10
+ appName?: string
11
+ navigate?: (href: string) => void
12
+ }>(), {
13
+ appName: 'Mantiq',
14
+ })
15
+
16
+ const email = ref('')
17
+ const password = ref('')
18
+ const error = ref('')
19
+ const loading = ref(false)
20
+
21
+ async function handleSubmit() {
22
+ error.value = ''
23
+ loading.value = true
24
+ const { ok, data } = await post('/login', { email: email.value, password: password.value })
25
+ if (ok) props.navigate?.('/dashboard')
26
+ else error.value = data?.error ?? 'Invalid email or password. Please try again.'
27
+ loading.value = false
28
+ }
29
+ </script>
30
+
31
+ <template>
32
+ <div class="min-h-screen bg-muted/50 flex items-center justify-center p-4">
33
+ <div class="w-full max-w-sm">
34
+ <!-- Logo -->
35
+ <div class="mb-8 flex flex-col items-center gap-3">
36
+ <div class="flex h-10 w-10 items-center justify-center rounded-lg bg-primary text-primary-foreground text-sm font-bold shadow-sm">
37
+ M
38
+ </div>
39
+ <span class="text-lg font-semibold tracking-tight">{{ appName }}</span>
40
+ </div>
41
+
42
+ <!-- Card -->
43
+ <div class="rounded-lg border bg-card shadow-sm p-6">
44
+ <div class="mb-6">
45
+ <h1 class="text-xl font-semibold tracking-tight">Sign in</h1>
46
+ <p class="mt-1 text-sm text-muted-foreground">
47
+ Enter your credentials to continue
48
+ </p>
49
+ </div>
50
+
51
+ <div v-if="error" class="mb-4 rounded-md border border-destructive px-4 py-3 text-sm text-destructive">
52
+ {{ error }}
53
+ </div>
54
+
55
+ <form class="space-y-4" @submit.prevent="handleSubmit">
56
+ <div class="space-y-2">
57
+ <Label for="email">Email</Label>
58
+ <Input
59
+ id="email"
60
+ v-model="email"
61
+ type="email"
62
+ required
63
+ placeholder="you@example.com"
64
+ autocomplete="email"
65
+ />
66
+ </div>
67
+ <div class="space-y-2">
68
+ <div class="flex items-center justify-between">
69
+ <Label for="password">Password</Label>
70
+ <button
71
+ type="button"
72
+ class="text-xs font-medium text-primary hover:text-primary/80"
73
+ tabindex="-1"
74
+ >
75
+ Forgot password?
76
+ </button>
77
+ </div>
78
+ <Input
79
+ id="password"
80
+ v-model="password"
81
+ type="password"
82
+ required
83
+ placeholder="Enter your password"
84
+ autocomplete="current-password"
85
+ />
86
+ </div>
87
+ <Button type="submit" class="w-full" :disabled="loading">
88
+ {{ loading ? 'Signing in...' : 'Sign in' }}
89
+ </Button>
90
+ </form>
91
+
92
+ <div class="relative my-6">
93
+ <div class="absolute inset-0 flex items-center">
94
+ <Separator class="w-full" />
95
+ </div>
96
+ <div class="relative flex justify-center text-xs uppercase">
97
+ <span class="bg-card px-2 text-muted-foreground">or</span>
98
+ </div>
99
+ </div>
100
+
101
+ <p class="text-center text-sm text-muted-foreground">
102
+ <button
103
+ type="button"
104
+ class="font-medium text-primary hover:text-primary/80"
105
+ @click="navigate?.('/register')"
106
+ >
107
+ Create an account
108
+ </button>
109
+ </p>
110
+ </div>
111
+
112
+ <!-- Footer -->
113
+ <p class="mt-6 text-center text-xs text-muted-foreground">
114
+ Powered by {{ appName }}
115
+ </p>
116
+ </div>
117
+ </div>
118
+ </template>
@@ -0,0 +1,139 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { post } from '@/lib/api'
4
+ import { Button } from '@/components/ui/button'
5
+ import { Input } from '@/components/ui/input'
6
+ import { Label } from '@/components/ui/label'
7
+ import { Separator } from '@/components/ui/separator'
8
+
9
+ const props = withDefaults(defineProps<{
10
+ appName?: string
11
+ navigate?: (href: string) => void
12
+ }>(), {
13
+ appName: 'Mantiq',
14
+ })
15
+
16
+ const name = ref('')
17
+ const email = ref('')
18
+ const password = ref('')
19
+ const error = ref('')
20
+ const loading = ref(false)
21
+
22
+ async function handleSubmit() {
23
+ error.value = ''
24
+ loading.value = true
25
+ const { ok, data } = await post('/register', { name: name.value, email: email.value, password: password.value })
26
+ if (ok) props.navigate?.('/dashboard')
27
+ else error.value = data?.error?.message ?? data?.error ?? 'Registration failed. Please try again.'
28
+ loading.value = false
29
+ }
30
+ </script>
31
+
32
+ <template>
33
+ <div class="min-h-screen bg-muted/50 flex items-center justify-center p-4">
34
+ <div class="w-full max-w-sm">
35
+ <!-- Logo -->
36
+ <div class="mb-8 flex flex-col items-center gap-3">
37
+ <div class="flex h-10 w-10 items-center justify-center rounded-lg bg-primary text-primary-foreground text-sm font-bold shadow-sm">
38
+ M
39
+ </div>
40
+ <span class="text-lg font-semibold tracking-tight">{{ appName }}</span>
41
+ </div>
42
+
43
+ <!-- Card -->
44
+ <div class="rounded-lg border bg-card shadow-sm p-6">
45
+ <div class="mb-6">
46
+ <h1 class="text-xl font-semibold tracking-tight">Create an account</h1>
47
+ <p class="mt-1 text-sm text-muted-foreground">
48
+ Get started with {{ appName }} today
49
+ </p>
50
+ </div>
51
+
52
+ <div v-if="error" class="mb-4 rounded-md border border-destructive px-4 py-3 text-sm text-destructive">
53
+ {{ error }}
54
+ </div>
55
+
56
+ <form class="space-y-4" @submit.prevent="handleSubmit">
57
+ <div class="space-y-2">
58
+ <Label for="name">Name</Label>
59
+ <Input
60
+ id="name"
61
+ v-model="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
+ v-model="email"
72
+ type="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
+ v-model="password"
83
+ type="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
+ @click="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>
139
+ </template>
@@ -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,68 @@
1
+ import {
2
+ Sidebar,
3
+ SidebarContent,
4
+ SidebarFooter,
5
+ SidebarHeader,
6
+ SidebarMenu,
7
+ SidebarMenuButton,
8
+ SidebarMenuItem,
9
+ } from '@/components/ui/sidebar'
10
+ import { NavGroup } from './nav-group'
11
+ import { NavUser, type NavUserProps } from './nav-user'
12
+ import { sidebarData } from './sidebar-data'
13
+ import { Command } from 'lucide-react'
14
+
15
+ export interface AppSidebarProps {
16
+ user: NavUserProps['user']
17
+ appName: string
18
+ activePath: string
19
+ navigate: (href: string) => void
20
+ onLogout: () => void
21
+ }
22
+
23
+ export function AppSidebar({
24
+ user,
25
+ appName,
26
+ activePath,
27
+ navigate,
28
+ onLogout,
29
+ }: AppSidebarProps) {
30
+ return (
31
+ <Sidebar variant="sidebar" collapsible="icon">
32
+ <SidebarHeader>
33
+ <SidebarMenu>
34
+ <SidebarMenuItem>
35
+ <SidebarMenuButton
36
+ size="lg"
37
+ className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
38
+ onClick={() => navigate('/dashboard')}
39
+ tooltip={appName}
40
+ >
41
+ <div className="flex aspect-square size-8 items-center justify-center rounded-none bg-sidebar-primary text-sidebar-primary-foreground">
42
+ <Command className="size-4" />
43
+ </div>
44
+ <div className="grid flex-1 text-left text-sm leading-tight">
45
+ <span className="truncate font-semibold">{appName}</span>
46
+ </div>
47
+ </SidebarMenuButton>
48
+ </SidebarMenuItem>
49
+ </SidebarMenu>
50
+ </SidebarHeader>
51
+
52
+ <SidebarContent>
53
+ {sidebarData.map((group) => (
54
+ <NavGroup
55
+ key={group.title}
56
+ group={group}
57
+ activePath={activePath}
58
+ navigate={navigate}
59
+ />
60
+ ))}
61
+ </SidebarContent>
62
+
63
+ <SidebarFooter>
64
+ <NavUser user={user} navigate={navigate} onLogout={onLogout} />
65
+ </SidebarFooter>
66
+ </Sidebar>
67
+ )
68
+ }
@@ -0,0 +1,42 @@
1
+ import { useCallback } from 'react'
2
+ import { post } from '@/lib/api'
3
+ import { SidebarProvider, SidebarInset } from '@/components/ui/sidebar'
4
+ import { AppSidebar } from './app-sidebar'
5
+
6
+ interface AuthenticatedLayoutProps {
7
+ children: React.ReactNode
8
+ currentUser?: { name: string; email: string; role?: string } | null
9
+ appName?: string
10
+ navigate: (href: string) => void
11
+ activePath: string
12
+ }
13
+
14
+ export function AuthenticatedLayout({
15
+ children,
16
+ currentUser,
17
+ appName = 'Mantiq',
18
+ navigate,
19
+ activePath,
20
+ }: AuthenticatedLayoutProps) {
21
+ const handleLogout = useCallback(async () => {
22
+ await post('/logout', {})
23
+ navigate('/login')
24
+ }, [navigate])
25
+
26
+ const user = currentUser ?? { name: 'User', email: 'user@example.com' }
27
+
28
+ return (
29
+ <SidebarProvider defaultOpen={false}>
30
+ <AppSidebar
31
+ user={user}
32
+ appName={appName}
33
+ activePath={activePath}
34
+ navigate={navigate}
35
+ onLogout={handleLogout}
36
+ />
37
+ <SidebarInset>
38
+ {children}
39
+ </SidebarInset>
40
+ </SidebarProvider>
41
+ )
42
+ }
@@ -0,0 +1,166 @@
1
+ import { AuthenticatedLayout } from '@/components/layout/authenticated-layout'
2
+ import { Header } from '@/components/layout/header'
3
+ import { Main } from '@/components/layout/main'
4
+ import {
5
+ Circle,
6
+ CheckCircle2,
7
+ ArrowUpRight,
8
+ Clock,
9
+ AlertCircle,
10
+ Minus,
11
+ } from 'lucide-react'
12
+
13
+ interface DashboardProps {
14
+ appName?: string
15
+ currentUser?: { id: number; name: string; email: string; role: string } | null
16
+ navigate: (href: string) => void
17
+ [key: string]: any
18
+ }
19
+
20
+ type Priority = 'urgent' | 'high' | 'medium' | 'low' | 'none'
21
+ type Status = 'done' | 'in-progress' | 'todo' | 'backlog'
22
+
23
+ const issues: {
24
+ id: string
25
+ title: string
26
+ status: Status
27
+ priority: Priority
28
+ assignee: string
29
+ updated: string
30
+ }[] = [
31
+ { id: 'MNT-142', title: 'Fix authentication token refresh race condition', status: 'in-progress', priority: 'urgent', assignee: 'AK', updated: '2m' },
32
+ { id: 'MNT-141', title: 'Add rate limiting to public API endpoints', status: 'todo', priority: 'high', assignee: 'SC', updated: '15m' },
33
+ { id: 'MNT-140', title: 'Migrate user sessions to Redis store', status: 'in-progress', priority: 'high', assignee: 'AK', updated: '1h' },
34
+ { id: 'MNT-139', title: 'Update Stripe webhook handler for v2024-12', status: 'todo', priority: 'medium', assignee: 'JL', updated: '2h' },
35
+ { id: 'MNT-138', title: 'Implement CSV export for analytics dashboard', status: 'done', priority: 'medium', assignee: 'NR', updated: '3h' },
36
+ { id: 'MNT-137', title: 'Add OpenTelemetry tracing to queue workers', status: 'backlog', priority: 'low', assignee: 'SC', updated: '5h' },
37
+ { id: 'MNT-136', title: 'Refactor notification preferences into per-channel config', status: 'done', priority: 'medium', assignee: 'AK', updated: '6h' },
38
+ { id: 'MNT-135', title: 'Set up staging environment with seed data', status: 'backlog', priority: 'low', assignee: 'JL', updated: '1d' },
39
+ { id: 'MNT-134', title: 'Write integration tests for payment flow', status: 'todo', priority: 'high', assignee: 'NR', updated: '1d' },
40
+ { id: 'MNT-133', title: 'Audit and update npm dependencies', status: 'backlog', priority: 'none', assignee: 'SC', updated: '2d' },
41
+ ]
42
+
43
+ const StatusIcon = ({ status }: { status: Status }) => {
44
+ switch (status) {
45
+ case 'done':
46
+ return <CheckCircle2 className="h-3.5 w-3.5 text-primary" />
47
+ case 'in-progress':
48
+ return <Clock className="h-3.5 w-3.5 text-amber-500" />
49
+ case 'todo':
50
+ return <Circle className="h-3.5 w-3.5 text-muted-foreground" />
51
+ case 'backlog':
52
+ return <Minus className="h-3.5 w-3.5 text-muted-foreground/50" />
53
+ }
54
+ }
55
+
56
+ const PriorityIndicator = ({ priority }: { priority: Priority }) => {
57
+ const colors: Record<Priority, string> = {
58
+ urgent: 'bg-red-500',
59
+ high: 'bg-orange-500',
60
+ medium: 'bg-yellow-500',
61
+ low: 'bg-blue-400',
62
+ none: 'bg-muted-foreground/30',
63
+ }
64
+ return (
65
+ <div className="flex items-center gap-0.5" title={priority}>
66
+ {[0, 1, 2, 3].map((i) => (
67
+ <div
68
+ key={i}
69
+ className={`h-2.5 w-[3px] rounded-[1px] ${
70
+ (priority === 'urgent' && i <= 3) ||
71
+ (priority === 'high' && i <= 2) ||
72
+ (priority === 'medium' && i <= 1) ||
73
+ (priority === 'low' && i <= 0)
74
+ ? colors[priority]
75
+ : 'bg-muted-foreground/15'
76
+ }`}
77
+ />
78
+ ))}
79
+ </div>
80
+ )
81
+ }
82
+
83
+ export default function Dashboard({
84
+ appName = 'Mantiq',
85
+ currentUser,
86
+ navigate,
87
+ }: DashboardProps) {
88
+ const doneCount = issues.filter((i) => i.status === 'done').length
89
+ const inProgressCount = issues.filter((i) => i.status === 'in-progress').length
90
+ const todoCount = issues.filter((i) => i.status === 'todo').length
91
+
92
+ return (
93
+ <AuthenticatedLayout
94
+ currentUser={currentUser}
95
+ appName={appName}
96
+ navigate={navigate}
97
+ activePath="/dashboard"
98
+ >
99
+ <Header navigate={navigate} />
100
+ <Main>
101
+ <div className="space-y-1">
102
+ {/* Compact header row */}
103
+ <div className="flex items-center justify-between py-3">
104
+ <div className="flex items-center gap-6">
105
+ <h2 className="text-sm font-semibold">My Issues</h2>
106
+ <div className="flex items-center gap-4 text-xs text-muted-foreground">
107
+ <span className="flex items-center gap-1.5">
108
+ <Clock className="h-3 w-3 text-amber-500" />
109
+ {inProgressCount} in progress
110
+ </span>
111
+ <span className="flex items-center gap-1.5">
112
+ <Circle className="h-3 w-3" />
113
+ {todoCount} todo
114
+ </span>
115
+ <span className="flex items-center gap-1.5">
116
+ <CheckCircle2 className="h-3 w-3 text-primary" />
117
+ {doneCount} done
118
+ </span>
119
+ </div>
120
+ </div>
121
+ <div className="flex items-center gap-2">
122
+ <kbd className="hidden sm:inline-flex items-center gap-0.5 rounded border bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
123
+ <span className="text-xs">⌘</span>K
124
+ </kbd>
125
+ </div>
126
+ </div>
127
+
128
+ {/* Issue list — no cards, just rows */}
129
+ <div className="border-t">
130
+ {issues.map((issue) => (
131
+ <div
132
+ key={issue.id}
133
+ className="group flex items-center gap-3 border-b px-1 py-2 transition-colors hover:bg-muted/50 cursor-default"
134
+ >
135
+ <PriorityIndicator priority={issue.priority} />
136
+ <StatusIcon status={issue.status} />
137
+ <span className="font-mono-num text-xs text-muted-foreground w-16 shrink-0">
138
+ {issue.id}
139
+ </span>
140
+ <span className={`flex-1 text-sm truncate ${issue.status === 'done' ? 'line-through text-muted-foreground' : ''}`}>
141
+ {issue.title}
142
+ </span>
143
+ <div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-muted text-[10px] font-medium text-muted-foreground">
144
+ {issue.assignee}
145
+ </div>
146
+ <span className="font-mono-num text-xs text-muted-foreground w-8 text-right shrink-0">
147
+ {issue.updated}
148
+ </span>
149
+ <ArrowUpRight className="h-3.5 w-3.5 text-muted-foreground/0 group-hover:text-muted-foreground transition-colors shrink-0" />
150
+ </div>
151
+ ))}
152
+ </div>
153
+
154
+ {/* Bottom counts */}
155
+ <div className="flex items-center justify-between pt-2 text-xs text-muted-foreground">
156
+ <span>{issues.length} issues</span>
157
+ <span className="flex items-center gap-1">
158
+ <AlertCircle className="h-3 w-3" />
159
+ {issues.filter((i) => i.priority === 'urgent').length} urgent
160
+ </span>
161
+ </div>
162
+ </div>
163
+ </Main>
164
+ </AuthenticatedLayout>
165
+ )
166
+ }