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,99 @@
1
+ <script lang="ts">
2
+ import { cn } from '$lib/utils'
3
+ import { Menu } from 'lucide-svelte'
4
+
5
+ interface TopNavLink {
6
+ title: string
7
+ href: string
8
+ isActive?: boolean
9
+ disabled?: boolean
10
+ }
11
+
12
+ let {
13
+ links,
14
+ onLinkClick,
15
+ class: className,
16
+ }: {
17
+ links: TopNavLink[]
18
+ onLinkClick?: (href: string) => void
19
+ class?: string
20
+ } = $props()
21
+
22
+ let mobileOpen = $state(false)
23
+
24
+ function handleClick(e: MouseEvent, href: string) {
25
+ if (onLinkClick) {
26
+ e.preventDefault()
27
+ onLinkClick(href)
28
+ }
29
+ }
30
+
31
+ function handleClickOutside(e: MouseEvent) {
32
+ const target = e.target as HTMLElement
33
+ if (!target.closest('[data-top-nav]')) {
34
+ mobileOpen = false
35
+ }
36
+ }
37
+
38
+ $effect(() => {
39
+ if (mobileOpen) {
40
+ document.addEventListener('click', handleClickOutside, true)
41
+ return () => document.removeEventListener('click', handleClickOutside, true)
42
+ }
43
+ })
44
+ </script>
45
+
46
+ <!-- Desktop navigation -->
47
+ <nav
48
+ class={cn(
49
+ 'hidden items-center gap-4 md:flex lg:gap-6',
50
+ className,
51
+ )}
52
+ >
53
+ {#each links as link}
54
+ <a
55
+ href={link.href}
56
+ onclick={(e: MouseEvent) => handleClick(e, link.href)}
57
+ class={cn(
58
+ 'text-sm font-medium transition-colors hover:text-primary',
59
+ link.isActive ? 'text-foreground' : 'text-muted-foreground',
60
+ link.disabled && 'pointer-events-none opacity-50',
61
+ )}
62
+ >
63
+ {link.title}
64
+ </a>
65
+ {/each}
66
+ </nav>
67
+
68
+ <!-- Mobile navigation -->
69
+ <div class="relative md:hidden" data-top-nav>
70
+ <button
71
+ type="button"
72
+ onclick={() => mobileOpen = !mobileOpen}
73
+ class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-input bg-background text-sm hover:bg-accent"
74
+ aria-label="Toggle navigation"
75
+ >
76
+ <Menu class="h-4 w-4" />
77
+ </button>
78
+ {#if mobileOpen}
79
+ <div class="absolute left-0 top-full z-50 mt-1 w-48 rounded-md border border-border bg-popover p-1 shadow-lg">
80
+ {#each links as link}
81
+ <button
82
+ type="button"
83
+ disabled={link.disabled}
84
+ onclick={(e: MouseEvent) => {
85
+ mobileOpen = false
86
+ if (onLinkClick) onLinkClick(link.href)
87
+ }}
88
+ class={cn(
89
+ 'flex w-full items-center rounded-md px-2 py-1.5 text-sm transition-colors hover:bg-accent',
90
+ !link.isActive && 'text-muted-foreground',
91
+ link.disabled && 'pointer-events-none opacity-50',
92
+ )}
93
+ >
94
+ {link.title}
95
+ </button>
96
+ {/each}
97
+ </div>
98
+ {/if}
99
+ </div>
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,192 @@
1
+ <script lang="ts">
2
+ import AuthenticatedLayout from '$lib/components/layout/authenticated-layout.svelte'
3
+ import Header from '$lib/components/layout/header.svelte'
4
+ import Main from '$lib/components/layout/main.svelte'
5
+ import TopNav from '$lib/components/layout/top-nav.svelte'
6
+ import { Download } from 'lucide-svelte'
7
+
8
+ let {
9
+ appName = 'Mantiq',
10
+ currentUser = null,
11
+ navigate,
12
+ }: {
13
+ appName?: string
14
+ currentUser?: { id: number; name: string; email: string } | null
15
+ navigate: (href: string) => void
16
+ [key: string]: any
17
+ } = $props()
18
+
19
+ const topNav = [
20
+ { title: 'Overview', href: '/dashboard', isActive: true },
21
+ { title: 'Sales', href: '/dashboard', isActive: false, disabled: true },
22
+ { title: 'Tickets', href: '/dashboard', isActive: false, disabled: true },
23
+ { title: 'Performance', href: '/dashboard', isActive: false, disabled: true },
24
+ ]
25
+
26
+ const recentSales = [
27
+ { name: 'Olivia Martin', email: 'olivia.martin@email.com', amount: '+$1,999.00' },
28
+ { name: 'Jackson Lee', email: 'jackson.lee@email.com', amount: '+$39.00' },
29
+ { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', amount: '+$299.00' },
30
+ { name: 'William Kim', email: 'will@email.com', amount: '+$99.00' },
31
+ { name: 'Sofia Davis', email: 'sofia.davis@email.com', amount: '+$39.00' },
32
+ ]
33
+
34
+ function getInitials(name: string) {
35
+ return name.split(' ').map((n) => n[0]).join('').toUpperCase().slice(0, 2)
36
+ }
37
+
38
+ const bars = [40, 30, 55, 45, 70, 60, 80, 50, 65, 45, 75, 55]
39
+ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
40
+ const maxH = 160
41
+ const barW = 32
42
+ const gap = 48
43
+ const svgW = bars.length * gap
44
+ const svgH = maxH + 30
45
+ </script>
46
+
47
+ <AuthenticatedLayout
48
+ {currentUser}
49
+ {appName}
50
+ {navigate}
51
+ activePath="/dashboard"
52
+ >
53
+ <Header {navigate}>
54
+ <TopNav links={topNav} onLinkClick={navigate} />
55
+ </Header>
56
+ <Main>
57
+ <div class="space-y-4">
58
+ <!-- Page title row -->
59
+ <div class="flex items-center justify-between">
60
+ <h2 class="text-3xl font-bold tracking-tight">Dashboard</h2>
61
+ <button
62
+ type="button"
63
+ disabled
64
+ class="inline-flex items-center gap-2 rounded-md border border-input bg-background px-3 py-1.5 text-sm font-medium hover:bg-accent disabled:pointer-events-none disabled:opacity-50"
65
+ >
66
+ <Download class="h-4 w-4" />
67
+ Download
68
+ </button>
69
+ </div>
70
+
71
+ <!-- Content -->
72
+ <div class="space-y-4">
73
+ <!-- Stat cards -->
74
+ <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
75
+ <!-- Total Revenue -->
76
+ <div class="rounded-xl border border-border bg-card p-6 text-card-foreground shadow-sm">
77
+ <div class="flex items-center justify-between pb-2">
78
+ <h3 class="text-sm font-medium">Total Revenue</h3>
79
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="h-4 w-4 text-muted-foreground">
80
+ <path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
81
+ </svg>
82
+ </div>
83
+ <div class="text-2xl font-bold">$45,231.89</div>
84
+ <p class="text-xs text-muted-foreground">+20.1% from last month</p>
85
+ </div>
86
+
87
+ <!-- Subscriptions -->
88
+ <div class="rounded-xl border border-border bg-card p-6 text-card-foreground shadow-sm">
89
+ <div class="flex items-center justify-between pb-2">
90
+ <h3 class="text-sm font-medium">Subscriptions</h3>
91
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="h-4 w-4 text-muted-foreground">
92
+ <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
93
+ <circle cx="9" cy="7" r="4" />
94
+ <path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" />
95
+ </svg>
96
+ </div>
97
+ <div class="text-2xl font-bold">+2,350</div>
98
+ <p class="text-xs text-muted-foreground">+180.1% from last month</p>
99
+ </div>
100
+
101
+ <!-- Sales -->
102
+ <div class="rounded-xl border border-border bg-card p-6 text-card-foreground shadow-sm">
103
+ <div class="flex items-center justify-between pb-2">
104
+ <h3 class="text-sm font-medium">Sales</h3>
105
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="h-4 w-4 text-muted-foreground">
106
+ <rect width="20" height="14" x="2" y="5" rx="2" />
107
+ <path d="M2 10h20" />
108
+ </svg>
109
+ </div>
110
+ <div class="text-2xl font-bold">+12,234</div>
111
+ <p class="text-xs text-muted-foreground">+19% from last month</p>
112
+ </div>
113
+
114
+ <!-- Active Now -->
115
+ <div class="rounded-xl border border-border bg-card p-6 text-card-foreground shadow-sm">
116
+ <div class="flex items-center justify-between pb-2">
117
+ <h3 class="text-sm font-medium">Active Now</h3>
118
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="h-4 w-4 text-muted-foreground">
119
+ <path d="M22 12h-4l-3 9L9 3l-3 9H2" />
120
+ </svg>
121
+ </div>
122
+ <div class="text-2xl font-bold">+573</div>
123
+ <p class="text-xs text-muted-foreground">+201 since last hour</p>
124
+ </div>
125
+ </div>
126
+
127
+ <!-- Bottom row: chart + recent sales -->
128
+ <div class="grid gap-4 lg:grid-cols-7">
129
+ <!-- Chart card -->
130
+ <div class="rounded-xl border border-border bg-card text-card-foreground shadow-sm lg:col-span-4">
131
+ <div class="p-6 pb-2">
132
+ <h3 class="text-lg font-semibold leading-none tracking-tight">Overview</h3>
133
+ </div>
134
+ <div class="p-6 pt-0 pl-2">
135
+ <svg
136
+ viewBox="0 0 {svgW} {svgH}"
137
+ class="h-[350px] w-full"
138
+ preserveAspectRatio="none"
139
+ >
140
+ {#each bars as h, i}
141
+ {@const barH = (h / 100) * maxH}
142
+ <g>
143
+ <rect
144
+ x={i * gap + (gap - barW) / 2}
145
+ y={maxH - barH}
146
+ width={barW}
147
+ height={barH}
148
+ rx={4}
149
+ class="fill-foreground/15"
150
+ />
151
+ <text
152
+ x={i * gap + gap / 2}
153
+ y={maxH + 18}
154
+ text-anchor="middle"
155
+ class="fill-muted-foreground text-[10px]"
156
+ >
157
+ {months[i]}
158
+ </text>
159
+ </g>
160
+ {/each}
161
+ </svg>
162
+ </div>
163
+ </div>
164
+
165
+ <!-- Recent sales card -->
166
+ <div class="rounded-xl border border-border bg-card text-card-foreground shadow-sm lg:col-span-3">
167
+ <div class="p-6 pb-2">
168
+ <h3 class="text-lg font-semibold leading-none tracking-tight">Recent Sales</h3>
169
+ <p class="text-sm text-muted-foreground mt-1.5">You made 265 sales this month.</p>
170
+ </div>
171
+ <div class="p-6 pt-0">
172
+ <div class="space-y-8">
173
+ {#each recentSales as sale}
174
+ <div class="flex items-center">
175
+ <div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-semibold">
176
+ {getInitials(sale.name)}
177
+ </div>
178
+ <div class="ml-4 space-y-1">
179
+ <p class="text-sm font-medium leading-none">{sale.name}</p>
180
+ <p class="text-sm text-muted-foreground">{sale.email}</p>
181
+ </div>
182
+ <div class="ml-auto font-medium">{sale.amount}</div>
183
+ </div>
184
+ {/each}
185
+ </div>
186
+ </div>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ </Main>
192
+ </AuthenticatedLayout>
@@ -0,0 +1,120 @@
1
+ <script lang="ts">
2
+ import { post } from '$lib/api'
3
+
4
+ let {
5
+ appName = 'Mantiq',
6
+ navigate,
7
+ }: {
8
+ appName?: string
9
+ navigate: (href: string) => void
10
+ [key: string]: any
11
+ } = $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 email or password. Please try again.'
25
+ loading = false
26
+ }
27
+ </script>
28
+
29
+ <div class="min-h-screen flex bg-background">
30
+ <!-- Left brand panel -->
31
+ <div class="hidden lg:flex lg:w-[45%] bg-foreground text-background flex-col justify-between p-10">
32
+ <div class="flex items-center gap-3">
33
+ <div class="flex h-8 w-8 items-center justify-center rounded bg-background text-foreground text-xs font-bold">
34
+ M
35
+ </div>
36
+ <span class="text-lg font-semibold tracking-tight">{appName}</span>
37
+ </div>
38
+
39
+ <div>
40
+ <blockquote class="text-2xl font-medium leading-snug tracking-tight">
41
+ "The framework that gets out of your way."
42
+ </blockquote>
43
+ </div>
44
+
45
+ <p class="text-sm text-background/50">
46
+ &copy; {new Date().getFullYear()} {appName}. All rights reserved.
47
+ </p>
48
+ </div>
49
+
50
+ <!-- Right form panel -->
51
+ <div class="flex flex-1 flex-col items-center justify-center px-6 py-12">
52
+ <!-- Mobile-only logo -->
53
+ <div class="mb-10 flex items-center gap-3 lg:hidden">
54
+ <div class="flex h-8 w-8 items-center justify-center rounded bg-foreground text-background text-xs font-bold">
55
+ M
56
+ </div>
57
+ <span class="text-lg font-semibold tracking-tight">{appName}</span>
58
+ </div>
59
+
60
+ <div class="w-full max-w-sm">
61
+ <div class="mb-8">
62
+ <h1 class="text-2xl font-semibold tracking-tight">Sign in</h1>
63
+ <p class="mt-2 text-sm text-muted-foreground">
64
+ Enter your credentials to continue
65
+ </p>
66
+ </div>
67
+
68
+ {#if error}
69
+ <div class="mb-6 rounded-md border border-destructive px-4 py-3 text-sm text-destructive">
70
+ {error}
71
+ </div>
72
+ {/if}
73
+
74
+ <form onsubmit={handleSubmit} class="space-y-4">
75
+ <div class="space-y-2">
76
+ <label for="email" class="text-sm font-medium leading-none">Email</label>
77
+ <input
78
+ id="email"
79
+ type="email"
80
+ bind:value={email}
81
+ required
82
+ placeholder="you@example.com"
83
+ autocomplete="email"
84
+ class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
85
+ />
86
+ </div>
87
+ <div class="space-y-2">
88
+ <label for="password" class="text-sm font-medium leading-none">Password</label>
89
+ <input
90
+ id="password"
91
+ type="password"
92
+ bind:value={password}
93
+ required
94
+ placeholder="Enter your password"
95
+ autocomplete="current-password"
96
+ class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
97
+ />
98
+ </div>
99
+ <button
100
+ type="submit"
101
+ class="inline-flex w-full items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50"
102
+ disabled={loading}
103
+ >
104
+ {loading ? 'Signing in...' : 'Sign in'}
105
+ </button>
106
+ </form>
107
+
108
+ <p class="mt-6 text-center text-sm text-muted-foreground">
109
+ Don't have an account?{' '}
110
+ <button
111
+ type="button"
112
+ class="font-medium text-foreground underline underline-offset-4"
113
+ onclick={() => navigate('/register')}
114
+ >
115
+ Register
116
+ </button>
117
+ </p>
118
+ </div>
119
+ </div>
120
+ </div>
@@ -0,0 +1,134 @@
1
+ <script lang="ts">
2
+ import { post } from '$lib/api'
3
+
4
+ let {
5
+ appName = 'Mantiq',
6
+ navigate,
7
+ }: {
8
+ appName?: string
9
+ navigate: (href: string) => void
10
+ [key: string]: any
11
+ } = $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?.message ?? data?.error ?? 'Registration failed. Please try again.'
26
+ loading = false
27
+ }
28
+ </script>
29
+
30
+ <div class="min-h-screen flex bg-background">
31
+ <!-- Left brand panel -->
32
+ <div class="hidden lg:flex lg:w-[45%] bg-foreground text-background flex-col justify-between p-10">
33
+ <div class="flex items-center gap-3">
34
+ <div class="flex h-8 w-8 items-center justify-center rounded bg-background text-foreground text-xs font-bold">
35
+ M
36
+ </div>
37
+ <span class="text-lg font-semibold tracking-tight">{appName}</span>
38
+ </div>
39
+
40
+ <div>
41
+ <blockquote class="text-2xl font-medium leading-snug tracking-tight">
42
+ "The framework that gets out of your way."
43
+ </blockquote>
44
+ </div>
45
+
46
+ <p class="text-sm text-background/50">
47
+ &copy; {new Date().getFullYear()} {appName}. All rights reserved.
48
+ </p>
49
+ </div>
50
+
51
+ <!-- Right form panel -->
52
+ <div class="flex flex-1 flex-col items-center justify-center px-6 py-12">
53
+ <!-- Mobile-only logo -->
54
+ <div class="mb-10 flex items-center gap-3 lg:hidden">
55
+ <div class="flex h-8 w-8 items-center justify-center rounded bg-foreground text-background text-xs font-bold">
56
+ M
57
+ </div>
58
+ <span class="text-lg font-semibold tracking-tight">{appName}</span>
59
+ </div>
60
+
61
+ <div class="w-full max-w-sm">
62
+ <div class="mb-8">
63
+ <h1 class="text-2xl font-semibold tracking-tight">Create an account</h1>
64
+ <p class="mt-2 text-sm text-muted-foreground">
65
+ Get started with {appName} today
66
+ </p>
67
+ </div>
68
+
69
+ {#if error}
70
+ <div class="mb-6 rounded-md border border-destructive px-4 py-3 text-sm text-destructive">
71
+ {error}
72
+ </div>
73
+ {/if}
74
+
75
+ <form onsubmit={handleSubmit} class="space-y-4">
76
+ <div class="space-y-2">
77
+ <label for="name" class="text-sm font-medium leading-none">Name</label>
78
+ <input
79
+ id="name"
80
+ bind:value={name}
81
+ required
82
+ placeholder="John Doe"
83
+ autocomplete="name"
84
+ class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
85
+ />
86
+ </div>
87
+ <div class="space-y-2">
88
+ <label for="email" class="text-sm font-medium leading-none">Email</label>
89
+ <input
90
+ id="email"
91
+ type="email"
92
+ bind:value={email}
93
+ required
94
+ placeholder="you@example.com"
95
+ autocomplete="email"
96
+ class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
97
+ />
98
+ </div>
99
+ <div class="space-y-2">
100
+ <label for="password" class="text-sm font-medium leading-none">Password</label>
101
+ <input
102
+ id="password"
103
+ type="password"
104
+ bind:value={password}
105
+ required
106
+ placeholder="Create a strong password"
107
+ autocomplete="new-password"
108
+ minlength={8}
109
+ class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
110
+ />
111
+ <p class="text-xs text-muted-foreground">Must be at least 8 characters</p>
112
+ </div>
113
+ <button
114
+ type="submit"
115
+ class="inline-flex w-full items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50"
116
+ disabled={loading}
117
+ >
118
+ {loading ? 'Creating account...' : 'Create account'}
119
+ </button>
120
+ </form>
121
+
122
+ <p class="mt-6 text-center text-sm text-muted-foreground">
123
+ Already have an account?{' '}
124
+ <button
125
+ type="button"
126
+ class="font-medium text-foreground underline underline-offset-4"
127
+ onclick={() => navigate('/login')}
128
+ >
129
+ Sign in
130
+ </button>
131
+ </p>
132
+ </div>
133
+ </div>
134
+ </div>