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,95 @@
1
+ import { useState } from 'react'
2
+ import { post } from '../lib/api.ts'
3
+ import { Button } from '@/components/ui/button'
4
+ import { Input } from '@/components/ui/input'
5
+ import { Label } from '@/components/ui/label'
6
+
7
+ interface LoginProps {
8
+ appName?: string
9
+ navigate: (href: string) => void
10
+ [key: string]: any
11
+ }
12
+
13
+ export default function Login({ appName = 'Mantiq', navigate }: LoginProps) {
14
+ const [email, setEmail] = useState('')
15
+ const [password, setPassword] = useState('')
16
+ const [error, setError] = useState('')
17
+ const [loading, setLoading] = useState(false)
18
+
19
+ const handleSubmit = async (e: React.FormEvent) => {
20
+ e.preventDefault()
21
+ setError('')
22
+ setLoading(true)
23
+ const { ok, data } = await post('/login', { email, password })
24
+ if (ok) navigate('/dashboard')
25
+ else setError(data?.error ?? 'Invalid credentials.')
26
+ setLoading(false)
27
+ }
28
+
29
+ return (
30
+ <div className="flex min-h-screen flex-col items-center justify-center bg-background px-4">
31
+ <div className="w-full max-w-[320px]">
32
+ {/* Logo — just text, minimal */}
33
+ <div className="mb-10">
34
+ <div className="flex items-center gap-2">
35
+ <div className="flex h-7 w-7 items-center justify-center rounded bg-foreground text-background text-xs font-bold">
36
+ M
37
+ </div>
38
+ <span className="text-sm font-semibold tracking-tight">{appName}</span>
39
+ </div>
40
+ </div>
41
+
42
+ <h1 className="text-sm font-medium mb-6">Sign in to continue</h1>
43
+
44
+ {error && (
45
+ <div className="mb-4 text-sm text-destructive">
46
+ {error}
47
+ </div>
48
+ )}
49
+
50
+ <form onSubmit={handleSubmit} className="space-y-3">
51
+ <div className="space-y-1.5">
52
+ <Label htmlFor="email" className="text-xs text-muted-foreground">Email</Label>
53
+ <Input
54
+ id="email"
55
+ type="email"
56
+ value={email}
57
+ onChange={(e) => setEmail(e.target.value)}
58
+ required
59
+ placeholder="you@example.com"
60
+ autoComplete="email"
61
+ className="h-8 text-sm"
62
+ />
63
+ </div>
64
+ <div className="space-y-1.5">
65
+ <Label htmlFor="password" className="text-xs text-muted-foreground">Password</Label>
66
+ <Input
67
+ id="password"
68
+ type="password"
69
+ value={password}
70
+ onChange={(e) => setPassword(e.target.value)}
71
+ required
72
+ placeholder="••••••••"
73
+ autoComplete="current-password"
74
+ className="h-8 text-sm"
75
+ />
76
+ </div>
77
+ <Button type="submit" className="w-full h-8 text-sm" disabled={loading}>
78
+ {loading ? 'Signing in…' : 'Continue'}
79
+ </Button>
80
+ </form>
81
+
82
+ <p className="mt-4 text-xs text-muted-foreground">
83
+ No account?{' '}
84
+ <button
85
+ type="button"
86
+ className="text-foreground hover:underline underline-offset-2"
87
+ onClick={() => navigate('/register')}
88
+ >
89
+ Create one
90
+ </button>
91
+ </p>
92
+ </div>
93
+ </div>
94
+ )
95
+ }
@@ -0,0 +1,73 @@
1
+ import { useState } from 'react'
2
+ import { post } from '../lib/api.ts'
3
+ import { Button } from '@/components/ui/button'
4
+ import { Input } from '@/components/ui/input'
5
+ import { Label } from '@/components/ui/label'
6
+
7
+ interface RegisterProps {
8
+ appName?: string
9
+ navigate: (href: string) => void
10
+ [key: string]: any
11
+ }
12
+
13
+ export default function Register({ appName = 'Mantiq', navigate }: RegisterProps) {
14
+ const [name, setName] = useState('')
15
+ const [email, setEmail] = useState('')
16
+ const [password, setPassword] = useState('')
17
+ const [error, setError] = useState('')
18
+ const [loading, setLoading] = useState(false)
19
+
20
+ const handleSubmit = async (e: React.FormEvent) => {
21
+ e.preventDefault()
22
+ setError('')
23
+ setLoading(true)
24
+ const { ok, data } = await post('/register', { name, email, password })
25
+ if (ok) navigate('/dashboard')
26
+ else setError(data?.error ?? 'Registration failed.')
27
+ setLoading(false)
28
+ }
29
+
30
+ return (
31
+ <div className="flex min-h-screen flex-col items-center justify-center bg-background px-4">
32
+ <div className="w-full max-w-[320px]">
33
+ <div className="mb-10">
34
+ <div className="flex items-center gap-2">
35
+ <div className="flex h-7 w-7 items-center justify-center rounded bg-foreground text-background text-xs font-bold">
36
+ M
37
+ </div>
38
+ <span className="text-sm font-semibold tracking-tight">{appName}</span>
39
+ </div>
40
+ </div>
41
+
42
+ <h1 className="text-sm font-medium mb-6">Create your account</h1>
43
+
44
+ {error && (
45
+ <div className="mb-4 text-sm text-destructive">{error}</div>
46
+ )}
47
+
48
+ <form onSubmit={handleSubmit} className="space-y-3">
49
+ <div className="space-y-1.5">
50
+ <Label htmlFor="name" className="text-xs text-muted-foreground">Name</Label>
51
+ <Input id="name" value={name} onChange={(e) => setName(e.target.value)} required placeholder="Your name" className="h-8 text-sm" />
52
+ </div>
53
+ <div className="space-y-1.5">
54
+ <Label htmlFor="email" className="text-xs text-muted-foreground">Email</Label>
55
+ <Input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} required placeholder="you@example.com" autoComplete="email" className="h-8 text-sm" />
56
+ </div>
57
+ <div className="space-y-1.5">
58
+ <Label htmlFor="password" className="text-xs text-muted-foreground">Password</Label>
59
+ <Input id="password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} required placeholder="••••••••" className="h-8 text-sm" />
60
+ </div>
61
+ <Button type="submit" className="w-full h-8 text-sm" disabled={loading}>
62
+ {loading ? 'Creating…' : 'Create account'}
63
+ </Button>
64
+ </form>
65
+
66
+ <p className="mt-4 text-xs text-muted-foreground">
67
+ Already have an account?{' '}
68
+ <button type="button" className="text-foreground hover:underline underline-offset-2" onClick={() => navigate('/login')}>Sign in</button>
69
+ </p>
70
+ </div>
71
+ </div>
72
+ )
73
+ }
@@ -0,0 +1,142 @@
1
+ @import "tailwindcss";
2
+ @custom-variant dark (&:where(.dark, .dark *));
3
+
4
+ /*
5
+ * shadcn/ui theme — Minimal
6
+ *
7
+ * Sharp, flat, dense. Zero border radius. Cards blend into background.
8
+ * Hairline borders, no shadows. Monospace numbers. Inspired by Linear/GitHub.
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: 0px;
52
+ --background: oklch(1 0 0);
53
+ --foreground: oklch(0.145 0.004 285.823);
54
+ --card: oklch(1 0 0);
55
+ --card-foreground: oklch(0.145 0.004 285.823);
56
+ --popover: oklch(1 0 0);
57
+ --popover-foreground: oklch(0.145 0.004 285.823);
58
+ --primary: oklch(0.546 0.245 262.881);
59
+ --primary-foreground: oklch(0.985 0 0);
60
+ --secondary: oklch(0.97 0.001 286.375);
61
+ --secondary-foreground: oklch(0.205 0.006 285.885);
62
+ --muted: oklch(0.97 0.001 286.375);
63
+ --muted-foreground: oklch(0.556 0.014 285.938);
64
+ --accent: oklch(0.97 0.001 286.375);
65
+ --accent-foreground: oklch(0.205 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.94 0.002 286.32);
69
+ --input: oklch(0.94 0.002 286.32);
70
+ --ring: oklch(0.546 0.245 262.881);
71
+ --chart-1: oklch(0.546 0.245 262.881);
72
+ --chart-2: oklch(0.6 0.118 184.714);
73
+ --chart-3: oklch(0.398 0.07 227.392);
74
+ --chart-4: oklch(0.828 0.189 84.429);
75
+ --chart-5: oklch(0.769 0.188 70.08);
76
+ --sidebar: oklch(0.99 0 0);
77
+ --sidebar-foreground: oklch(0.145 0.004 285.823);
78
+ --sidebar-primary: oklch(0.546 0.245 262.881);
79
+ --sidebar-primary-foreground: oklch(0.985 0 0);
80
+ --sidebar-accent: oklch(0.97 0.001 286.375);
81
+ --sidebar-accent-foreground: oklch(0.205 0.006 285.885);
82
+ --sidebar-border: oklch(0.94 0.002 286.32);
83
+ --sidebar-ring: oklch(0.546 0.245 262.881);
84
+ }
85
+
86
+ .dark {
87
+ --background: oklch(0.13 0.004 285.823);
88
+ --foreground: oklch(0.985 0 0);
89
+ --card: oklch(0.13 0.004 285.823);
90
+ --card-foreground: oklch(0.985 0 0);
91
+ --popover: oklch(0.155 0.004 285.823);
92
+ --popover-foreground: oklch(0.985 0 0);
93
+ --primary: oklch(0.623 0.214 262.881);
94
+ --primary-foreground: oklch(0.13 0.004 285.823);
95
+ --secondary: oklch(0.2 0.006 286.033);
96
+ --secondary-foreground: oklch(0.985 0 0);
97
+ --muted: oklch(0.2 0.006 286.033);
98
+ --muted-foreground: oklch(0.63 0.014 286.067);
99
+ --accent: oklch(0.2 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.22 0.005 286.033);
104
+ --input: oklch(0.22 0.005 286.033);
105
+ --ring: oklch(0.623 0.214 262.881);
106
+ --chart-1: oklch(0.623 0.214 262.881);
107
+ --chart-2: oklch(0.696 0.17 162.48);
108
+ --chart-3: oklch(0.769 0.188 70.08);
109
+ --chart-4: oklch(0.627 0.265 303.9);
110
+ --chart-5: oklch(0.645 0.246 16.439);
111
+ --sidebar: oklch(0.13 0.004 285.823);
112
+ --sidebar-foreground: oklch(0.985 0 0);
113
+ --sidebar-primary: oklch(0.623 0.214 262.881);
114
+ --sidebar-primary-foreground: oklch(0.13 0.004 285.823);
115
+ --sidebar-accent: oklch(0.2 0.006 286.033);
116
+ --sidebar-accent-foreground: oklch(0.985 0 0);
117
+ --sidebar-border: oklch(0.22 0.005 286.033);
118
+ --sidebar-ring: oklch(0.623 0.214 262.881);
119
+ }
120
+
121
+ @layer base {
122
+ * {
123
+ @apply border-border;
124
+ }
125
+ body {
126
+ @apply bg-background text-foreground;
127
+ font-feature-settings: "ss01", "ss02", "cv01";
128
+ -webkit-font-smoothing: antialiased;
129
+ }
130
+ }
131
+
132
+ /* Monospace tabular figures for numbers */
133
+ .font-mono-num {
134
+ font-variant-numeric: tabular-nums;
135
+ font-feature-settings: "tnum";
136
+ }
137
+
138
+ @keyframes fadeUp {
139
+ from { opacity: 0; transform: translateY(8px); }
140
+ to { opacity: 1; transform: translateY(0); }
141
+ }
142
+ .animate-fade-up { animation: fadeUp 0.3s ease-out; }
@@ -0,0 +1,149 @@
1
+ <script lang="ts">
2
+ import AuthenticatedLayout from '$lib/components/layout/AuthenticatedLayout.svelte'
3
+ import Header from '$lib/components/layout/Header.svelte'
4
+ import Main from '$lib/components/layout/Main.svelte'
5
+ import {
6
+ Circle,
7
+ CheckCircle2,
8
+ ArrowUpRight,
9
+ Clock,
10
+ AlertCircle,
11
+ Minus,
12
+ } from 'lucide-svelte'
13
+
14
+ interface Props {
15
+ appName?: string
16
+ currentUser?: { id: number; name: string; email: string; role: string } | null
17
+ navigate?: (href: string) => void
18
+ }
19
+ let { appName = 'Mantiq', currentUser = null, navigate = () => {} }: Props = $props()
20
+
21
+ type Priority = 'urgent' | 'high' | 'medium' | 'low' | 'none'
22
+ type Status = 'done' | 'in-progress' | 'todo' | 'backlog'
23
+
24
+ const issues: {
25
+ id: string
26
+ title: string
27
+ status: Status
28
+ priority: Priority
29
+ assignee: string
30
+ updated: string
31
+ }[] = [
32
+ { id: 'MNT-142', title: 'Fix authentication token refresh race condition', status: 'in-progress', priority: 'urgent', assignee: 'AK', updated: '2m' },
33
+ { id: 'MNT-141', title: 'Add rate limiting to public API endpoints', status: 'todo', priority: 'high', assignee: 'SC', updated: '15m' },
34
+ { id: 'MNT-140', title: 'Migrate user sessions to Redis store', status: 'in-progress', priority: 'high', assignee: 'AK', updated: '1h' },
35
+ { id: 'MNT-139', title: 'Update Stripe webhook handler for v2024-12', status: 'todo', priority: 'medium', assignee: 'JL', updated: '2h' },
36
+ { id: 'MNT-138', title: 'Implement CSV export for analytics dashboard', status: 'done', priority: 'medium', assignee: 'NR', updated: '3h' },
37
+ { id: 'MNT-137', title: 'Add OpenTelemetry tracing to queue workers', status: 'backlog', priority: 'low', assignee: 'SC', updated: '5h' },
38
+ { id: 'MNT-136', title: 'Refactor notification preferences into per-channel config', status: 'done', priority: 'medium', assignee: 'AK', updated: '6h' },
39
+ { id: 'MNT-135', title: 'Set up staging environment with seed data', status: 'backlog', priority: 'low', assignee: 'JL', updated: '1d' },
40
+ { id: 'MNT-134', title: 'Write integration tests for payment flow', status: 'todo', priority: 'high', assignee: 'NR', updated: '1d' },
41
+ { id: 'MNT-133', title: 'Audit and update npm dependencies', status: 'backlog', priority: 'none', assignee: 'SC', updated: '2d' },
42
+ ]
43
+
44
+ const priorityColors: Record<Priority, string> = {
45
+ urgent: 'bg-red-500',
46
+ high: 'bg-orange-500',
47
+ medium: 'bg-yellow-500',
48
+ low: 'bg-blue-400',
49
+ none: 'bg-muted-foreground/30',
50
+ }
51
+
52
+ function isPriorityBarActive(priority: Priority, index: number): boolean {
53
+ return (
54
+ (priority === 'urgent' && index <= 3) ||
55
+ (priority === 'high' && index <= 2) ||
56
+ (priority === 'medium' && index <= 1) ||
57
+ (priority === 'low' && index <= 0)
58
+ )
59
+ }
60
+
61
+ const doneCount = issues.filter((i) => i.status === 'done').length
62
+ const inProgressCount = issues.filter((i) => i.status === 'in-progress').length
63
+ const todoCount = issues.filter((i) => i.status === 'todo').length
64
+ </script>
65
+
66
+ <AuthenticatedLayout {currentUser} {appName} {navigate} activePath="/dashboard">
67
+ <Header {navigate} />
68
+ <Main>
69
+ <div class="space-y-1">
70
+ <!-- Compact header row -->
71
+ <div class="flex items-center justify-between py-3">
72
+ <div class="flex items-center gap-6">
73
+ <h2 class="text-sm font-semibold">My Issues</h2>
74
+ <div class="flex items-center gap-4 text-xs text-muted-foreground">
75
+ <span class="flex items-center gap-1.5">
76
+ <Clock class="h-3 w-3 text-amber-500" />
77
+ {inProgressCount} in progress
78
+ </span>
79
+ <span class="flex items-center gap-1.5">
80
+ <Circle class="h-3 w-3" />
81
+ {todoCount} todo
82
+ </span>
83
+ <span class="flex items-center gap-1.5">
84
+ <CheckCircle2 class="h-3 w-3 text-primary" />
85
+ {doneCount} done
86
+ </span>
87
+ </div>
88
+ </div>
89
+ <div class="flex items-center gap-2">
90
+ <kbd class="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">
91
+ <span class="text-xs">&#8984;</span>K
92
+ </kbd>
93
+ </div>
94
+ </div>
95
+
96
+ <!-- Issue list — no cards, just rows -->
97
+ <div class="border-t">
98
+ {#each issues as issue (issue.id)}
99
+ <div class="group flex items-center gap-3 border-b px-1 py-2 transition-colors hover:bg-muted/50 cursor-default">
100
+ <!-- Priority indicator -->
101
+ <div class="flex items-center gap-0.5" title={issue.priority}>
102
+ {#each [0, 1, 2, 3] as i}
103
+ <div
104
+ class="h-2.5 w-[3px] rounded-[1px] {isPriorityBarActive(issue.priority, i)
105
+ ? priorityColors[issue.priority]
106
+ : 'bg-muted-foreground/15'}"
107
+ ></div>
108
+ {/each}
109
+ </div>
110
+
111
+ <!-- Status icon -->
112
+ {#if issue.status === 'done'}
113
+ <CheckCircle2 class="h-3.5 w-3.5 text-primary" />
114
+ {:else if issue.status === 'in-progress'}
115
+ <Clock class="h-3.5 w-3.5 text-amber-500" />
116
+ {:else if issue.status === 'todo'}
117
+ <Circle class="h-3.5 w-3.5 text-muted-foreground" />
118
+ {:else}
119
+ <Minus class="h-3.5 w-3.5 text-muted-foreground/50" />
120
+ {/if}
121
+
122
+ <span class="font-mono-num text-xs text-muted-foreground w-16 shrink-0">
123
+ {issue.id}
124
+ </span>
125
+ <span class="flex-1 text-sm truncate {issue.status === 'done' ? 'line-through text-muted-foreground' : ''}">
126
+ {issue.title}
127
+ </span>
128
+ <div class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-muted text-[10px] font-medium text-muted-foreground">
129
+ {issue.assignee}
130
+ </div>
131
+ <span class="font-mono-num text-xs text-muted-foreground w-8 text-right shrink-0">
132
+ {issue.updated}
133
+ </span>
134
+ <ArrowUpRight class="h-3.5 w-3.5 text-muted-foreground/0 group-hover:text-muted-foreground transition-colors shrink-0" />
135
+ </div>
136
+ {/each}
137
+ </div>
138
+
139
+ <!-- Bottom counts -->
140
+ <div class="flex items-center justify-between pt-2 text-xs text-muted-foreground">
141
+ <span>{issues.length} issues</span>
142
+ <span class="flex items-center gap-1">
143
+ <AlertCircle class="h-3 w-3" />
144
+ {issues.filter((i) => i.priority === 'urgent').length} urgent
145
+ </span>
146
+ </div>
147
+ </div>
148
+ </Main>
149
+ </AuthenticatedLayout>
@@ -0,0 +1,90 @@
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
+
7
+ interface Props {
8
+ appName?: string
9
+ navigate?: (href: string) => void
10
+ }
11
+ let { appName = 'Mantiq', navigate = () => {} }: Props = $props()
12
+
13
+ let email = $state('')
14
+ let password = $state('')
15
+ let error = $state('')
16
+ let loading = $state(false)
17
+
18
+ async function handleSubmit(e: SubmitEvent) {
19
+ e.preventDefault()
20
+ error = ''
21
+ loading = true
22
+ const { ok, data } = await post('/login', { email, password })
23
+ if (ok) navigate('/dashboard')
24
+ else error = data?.error ?? 'Invalid credentials.'
25
+ loading = false
26
+ }
27
+ </script>
28
+
29
+ <div class="flex min-h-screen flex-col items-center justify-center bg-background px-4">
30
+ <div class="w-full max-w-[320px]">
31
+ <!-- Logo — just text, minimal -->
32
+ <div class="mb-10">
33
+ <div class="flex items-center gap-2">
34
+ <div class="flex h-7 w-7 items-center justify-center rounded bg-foreground text-background text-xs font-bold">
35
+ M
36
+ </div>
37
+ <span class="text-sm font-semibold tracking-tight">{appName}</span>
38
+ </div>
39
+ </div>
40
+
41
+ <h1 class="text-sm font-medium mb-6">Sign in to continue</h1>
42
+
43
+ {#if error}
44
+ <div class="mb-4 text-sm text-destructive">
45
+ {error}
46
+ </div>
47
+ {/if}
48
+
49
+ <form onsubmit={handleSubmit} class="space-y-3">
50
+ <div class="space-y-1.5">
51
+ <Label for="email" class="text-xs text-muted-foreground">Email</Label>
52
+ <Input
53
+ id="email"
54
+ type="email"
55
+ bind:value={email}
56
+ required
57
+ placeholder="you@example.com"
58
+ autocomplete="email"
59
+ class="h-8 text-sm"
60
+ />
61
+ </div>
62
+ <div class="space-y-1.5">
63
+ <Label for="password" class="text-xs text-muted-foreground">Password</Label>
64
+ <Input
65
+ id="password"
66
+ type="password"
67
+ bind:value={password}
68
+ required
69
+ placeholder="••••••••"
70
+ autocomplete="current-password"
71
+ class="h-8 text-sm"
72
+ />
73
+ </div>
74
+ <Button type="submit" class="w-full h-8 text-sm" disabled={loading}>
75
+ {loading ? 'Signing in…' : 'Continue'}
76
+ </Button>
77
+ </form>
78
+
79
+ <p class="mt-4 text-xs text-muted-foreground">
80
+ No account?{' '}
81
+ <button
82
+ type="button"
83
+ class="text-foreground hover:underline underline-offset-2"
84
+ onclick={() => navigate('/register')}
85
+ >
86
+ Create one
87
+ </button>
88
+ </p>
89
+ </div>
90
+ </div>
@@ -0,0 +1,70 @@
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
+
7
+ interface Props {
8
+ appName?: string
9
+ navigate?: (href: string) => void
10
+ }
11
+ let { appName = 'Mantiq', navigate = () => {} }: Props = $props()
12
+
13
+ let name = $state('')
14
+ let email = $state('')
15
+ let password = $state('')
16
+ let error = $state('')
17
+ let loading = $state(false)
18
+
19
+ async function handleSubmit(e: SubmitEvent) {
20
+ e.preventDefault()
21
+ error = ''
22
+ loading = true
23
+ const { ok, data } = await post('/register', { name, email, password })
24
+ if (ok) navigate('/dashboard')
25
+ else error = data?.error ?? 'Registration failed.'
26
+ loading = false
27
+ }
28
+ </script>
29
+
30
+ <div class="flex min-h-screen flex-col items-center justify-center bg-background px-4">
31
+ <div class="w-full max-w-[320px]">
32
+ <div class="mb-10">
33
+ <div class="flex items-center gap-2">
34
+ <div class="flex h-7 w-7 items-center justify-center rounded bg-foreground text-background text-xs font-bold">
35
+ M
36
+ </div>
37
+ <span class="text-sm font-semibold tracking-tight">{appName}</span>
38
+ </div>
39
+ </div>
40
+
41
+ <h1 class="text-sm font-medium mb-6">Create your account</h1>
42
+
43
+ {#if error}
44
+ <div class="mb-4 text-sm text-destructive">{error}</div>
45
+ {/if}
46
+
47
+ <form onsubmit={handleSubmit} class="space-y-3">
48
+ <div class="space-y-1.5">
49
+ <Label for="name" class="text-xs text-muted-foreground">Name</Label>
50
+ <Input id="name" bind:value={name} required placeholder="Your name" class="h-8 text-sm" />
51
+ </div>
52
+ <div class="space-y-1.5">
53
+ <Label for="email" class="text-xs text-muted-foreground">Email</Label>
54
+ <Input id="email" type="email" bind:value={email} required placeholder="you@example.com" autocomplete="email" class="h-8 text-sm" />
55
+ </div>
56
+ <div class="space-y-1.5">
57
+ <Label for="password" class="text-xs text-muted-foreground">Password</Label>
58
+ <Input id="password" type="password" bind:value={password} required placeholder="••••••••" class="h-8 text-sm" />
59
+ </div>
60
+ <Button type="submit" class="w-full h-8 text-sm" disabled={loading}>
61
+ {loading ? 'Creating…' : 'Create account'}
62
+ </Button>
63
+ </form>
64
+
65
+ <p class="mt-4 text-xs text-muted-foreground">
66
+ Already have an account?{' '}
67
+ <button type="button" class="text-foreground hover:underline underline-offset-2" onclick={() => navigate('/login')}>Sign in</button>
68
+ </p>
69
+ </div>
70
+ </div>