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,205 @@
1
+ import { AuthenticatedLayout } from '@/components/layout/authenticated-layout'
2
+ import { Header } from '@/components/layout/header'
3
+ import { Main } from '@/components/layout/main'
4
+ import { TopNav } from '@/components/layout/top-nav'
5
+
6
+ interface DashboardProps {
7
+ appName?: string
8
+ currentUser?: { id: number; name: string; email: string; role: string } | null
9
+ navigate: (href: string) => void
10
+ [key: string]: any
11
+ }
12
+
13
+ const topNav = [
14
+ { title: 'Overview', href: '/dashboard', isActive: true },
15
+ { title: 'Sales', href: '/dashboard', isActive: false, disabled: true },
16
+ { title: 'Tickets', href: '/dashboard', isActive: false, disabled: true },
17
+ { title: 'Performance', href: '/dashboard', isActive: false, disabled: true },
18
+ ]
19
+
20
+ const recentSales = [
21
+ { name: 'Olivia Martin', email: 'olivia.martin@email.com', amount: '+$1,999.00' },
22
+ { name: 'Jackson Lee', email: 'jackson.lee@email.com', amount: '+$39.00' },
23
+ { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', amount: '+$299.00' },
24
+ { name: 'William Kim', email: 'will@email.com', amount: '+$99.00' },
25
+ { name: 'Sofia Davis', email: 'sofia.davis@email.com', amount: '+$39.00' },
26
+ ]
27
+
28
+ function getInitials(name: string) {
29
+ return name
30
+ .split(' ')
31
+ .map((n) => n[0])
32
+ .join('')
33
+ .toUpperCase()
34
+ .slice(0, 2)
35
+ }
36
+
37
+ function OverviewChart() {
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
+
46
+ return (
47
+ <svg
48
+ viewBox={`0 0 ${svgW} ${svgH}`}
49
+ className="h-[350px] w-full"
50
+ preserveAspectRatio="none"
51
+ >
52
+ {bars.map((h, i) => {
53
+ const barH = (h / 100) * maxH
54
+ return (
55
+ <g key={i}>
56
+ <rect
57
+ x={i * gap + (gap - barW) / 2}
58
+ y={maxH - barH}
59
+ width={barW}
60
+ height={barH}
61
+ rx={4}
62
+ className="fill-gray-900/15 dark:fill-gray-50/15"
63
+ />
64
+ <text
65
+ x={i * gap + gap / 2}
66
+ y={maxH + 18}
67
+ textAnchor="middle"
68
+ className="fill-gray-500 text-[10px] dark:fill-gray-400"
69
+ >
70
+ {months[i]}
71
+ </text>
72
+ </g>
73
+ )
74
+ })}
75
+ </svg>
76
+ )
77
+ }
78
+
79
+ export default function Dashboard({
80
+ appName = 'Mantiq',
81
+ currentUser,
82
+ navigate,
83
+ }: DashboardProps) {
84
+ return (
85
+ <AuthenticatedLayout
86
+ currentUser={currentUser}
87
+ appName={appName}
88
+ navigate={navigate}
89
+ activePath="/dashboard"
90
+ >
91
+ <Header navigate={navigate}>
92
+ <TopNav links={topNav} onLinkClick={navigate} />
93
+ </Header>
94
+ <Main>
95
+ <div className="space-y-4">
96
+ {/* Page title row */}
97
+ <div className="flex items-center justify-between">
98
+ <h2 className="text-3xl font-bold tracking-tight text-gray-900 dark:text-gray-50">Dashboard</h2>
99
+ <button
100
+ type="button"
101
+ disabled
102
+ className="inline-flex items-center gap-2 rounded-md border border-gray-200 px-3 py-1.5 text-sm font-medium text-gray-500 opacity-50 dark:border-gray-700 dark:text-gray-400"
103
+ >
104
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-4 w-4">
105
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3" />
106
+ </svg>
107
+ Download
108
+ </button>
109
+ </div>
110
+
111
+ {/* Content */}
112
+ <div className="space-y-4">
113
+ {/* Stat cards */}
114
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
115
+ {/* Total Revenue */}
116
+ <div className="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-800 dark:bg-gray-950">
117
+ <div className="flex items-center justify-between pb-2">
118
+ <h3 className="text-sm font-medium text-gray-900 dark:text-gray-50">Total Revenue</h3>
119
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" className="h-4 w-4 text-gray-500 dark:text-gray-400">
120
+ <path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
121
+ </svg>
122
+ </div>
123
+ <div className="text-2xl font-bold text-gray-900 dark:text-gray-50">$45,231.89</div>
124
+ <p className="text-xs text-gray-500 dark:text-gray-400">+20.1% from last month</p>
125
+ </div>
126
+
127
+ {/* Subscriptions */}
128
+ <div className="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-800 dark:bg-gray-950">
129
+ <div className="flex items-center justify-between pb-2">
130
+ <h3 className="text-sm font-medium text-gray-900 dark:text-gray-50">Subscriptions</h3>
131
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" className="h-4 w-4 text-gray-500 dark:text-gray-400">
132
+ <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" />
133
+ </svg>
134
+ </div>
135
+ <div className="text-2xl font-bold text-gray-900 dark:text-gray-50">+2,350</div>
136
+ <p className="text-xs text-gray-500 dark:text-gray-400">+180.1% from last month</p>
137
+ </div>
138
+
139
+ {/* Sales */}
140
+ <div className="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-800 dark:bg-gray-950">
141
+ <div className="flex items-center justify-between pb-2">
142
+ <h3 className="text-sm font-medium text-gray-900 dark:text-gray-50">Sales</h3>
143
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" className="h-4 w-4 text-gray-500 dark:text-gray-400">
144
+ <rect width="20" height="14" x="2" y="5" rx="2" /><path d="M2 10h20" />
145
+ </svg>
146
+ </div>
147
+ <div className="text-2xl font-bold text-gray-900 dark:text-gray-50">+12,234</div>
148
+ <p className="text-xs text-gray-500 dark:text-gray-400">+19% from last month</p>
149
+ </div>
150
+
151
+ {/* Active Now */}
152
+ <div className="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-800 dark:bg-gray-950">
153
+ <div className="flex items-center justify-between pb-2">
154
+ <h3 className="text-sm font-medium text-gray-900 dark:text-gray-50">Active Now</h3>
155
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" className="h-4 w-4 text-gray-500 dark:text-gray-400">
156
+ <path d="M22 12h-4l-3 9L9 3l-3 9H2" />
157
+ </svg>
158
+ </div>
159
+ <div className="text-2xl font-bold text-gray-900 dark:text-gray-50">+573</div>
160
+ <p className="text-xs text-gray-500 dark:text-gray-400">+201 since last hour</p>
161
+ </div>
162
+ </div>
163
+
164
+ {/* Bottom row: chart + recent sales */}
165
+ <div className="grid gap-4 lg:grid-cols-7">
166
+ {/* Chart card */}
167
+ <div className="rounded-lg border border-gray-200 bg-white dark:border-gray-800 dark:bg-gray-950 lg:col-span-4">
168
+ <div className="p-6 pb-2">
169
+ <h3 className="text-lg font-semibold text-gray-900 dark:text-gray-50">Overview</h3>
170
+ </div>
171
+ <div className="p-6 pt-0 pl-2">
172
+ <OverviewChart />
173
+ </div>
174
+ </div>
175
+
176
+ {/* Recent sales card */}
177
+ <div className="rounded-lg border border-gray-200 bg-white dark:border-gray-800 dark:bg-gray-950 lg:col-span-3">
178
+ <div className="p-6 pb-2">
179
+ <h3 className="text-lg font-semibold text-gray-900 dark:text-gray-50">Recent Sales</h3>
180
+ <p className="text-sm text-gray-500 dark:text-gray-400">You made 265 sales this month.</p>
181
+ </div>
182
+ <div className="p-6 pt-0">
183
+ <div className="space-y-8">
184
+ {recentSales.map((sale) => (
185
+ <div key={sale.email} className="flex items-center">
186
+ <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-gray-100 text-xs font-semibold text-gray-700 dark:bg-gray-800 dark:text-gray-300">
187
+ {getInitials(sale.name)}
188
+ </div>
189
+ <div className="ml-4 space-y-1">
190
+ <p className="text-sm font-medium leading-none text-gray-900 dark:text-gray-50">{sale.name}</p>
191
+ <p className="text-sm text-gray-500 dark:text-gray-400">{sale.email}</p>
192
+ </div>
193
+ <div className="ml-auto font-medium text-gray-900 dark:text-gray-50">{sale.amount}</div>
194
+ </div>
195
+ ))}
196
+ </div>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ </div>
201
+ </div>
202
+ </Main>
203
+ </AuthenticatedLayout>
204
+ )
205
+ }
@@ -0,0 +1,122 @@
1
+ import { useState } from 'react'
2
+ import { post } from '../lib/api.ts'
3
+
4
+ interface LoginProps {
5
+ appName?: string
6
+ navigate: (href: string) => void
7
+ [key: string]: any
8
+ }
9
+
10
+ export default function Login({ appName = 'Mantiq', navigate }: LoginProps) {
11
+ const [email, setEmail] = useState('')
12
+ const [password, setPassword] = useState('')
13
+ const [error, setError] = useState('')
14
+ const [loading, setLoading] = useState(false)
15
+
16
+ const handleSubmit = async (e: React.FormEvent) => {
17
+ e.preventDefault()
18
+ setError('')
19
+ setLoading(true)
20
+ const { ok, data } = await post('/login', { email, password })
21
+ if (ok) navigate('/dashboard')
22
+ else setError(data?.error ?? 'Invalid email or password. Please try again.')
23
+ setLoading(false)
24
+ }
25
+
26
+ return (
27
+ <div className="flex min-h-screen bg-white dark:bg-gray-950">
28
+ {/* Left brand panel */}
29
+ <div className="hidden lg:flex lg:w-[45%] flex-col justify-between bg-gray-900 p-10 text-white dark:bg-gray-900">
30
+ <div className="flex items-center gap-3">
31
+ <div className="flex h-8 w-8 items-center justify-center rounded bg-white text-xs font-bold text-gray-900">
32
+ M
33
+ </div>
34
+ <span className="text-lg font-semibold tracking-tight">{appName}</span>
35
+ </div>
36
+
37
+ <div>
38
+ <blockquote className="text-2xl font-medium leading-snug tracking-tight">
39
+ "The framework that gets out of your way."
40
+ </blockquote>
41
+ </div>
42
+
43
+ <p className="text-sm text-white/50">
44
+ &copy; {new Date().getFullYear()} {appName}. All rights reserved.
45
+ </p>
46
+ </div>
47
+
48
+ {/* Right form panel */}
49
+ <div className="flex flex-1 flex-col items-center justify-center px-6 py-12">
50
+ {/* Mobile-only logo */}
51
+ <div className="mb-10 flex items-center gap-3 lg:hidden">
52
+ <div className="flex h-8 w-8 items-center justify-center rounded bg-gray-900 text-xs font-bold text-white dark:bg-gray-50 dark:text-gray-900">
53
+ M
54
+ </div>
55
+ <span className="text-lg font-semibold tracking-tight text-gray-900 dark:text-gray-50">{appName}</span>
56
+ </div>
57
+
58
+ <div className="w-full max-w-sm">
59
+ <div className="mb-8">
60
+ <h1 className="text-2xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">Sign in</h1>
61
+ <p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
62
+ Enter your credentials to continue
63
+ </p>
64
+ </div>
65
+
66
+ {error && (
67
+ <div className="mb-6 rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600 dark:border-red-800 dark:bg-red-950 dark:text-red-400">
68
+ {error}
69
+ </div>
70
+ )}
71
+
72
+ <form onSubmit={handleSubmit} className="space-y-4">
73
+ <div className="space-y-2">
74
+ <label htmlFor="email" className="text-sm font-medium text-gray-900 dark:text-gray-50">Email</label>
75
+ <input
76
+ id="email"
77
+ type="email"
78
+ value={email}
79
+ onChange={(e) => setEmail(e.target.value)}
80
+ required
81
+ placeholder="you@example.com"
82
+ autoComplete="email"
83
+ className="w-full rounded-md border border-gray-200 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 outline-none ring-emerald-500 transition focus:border-emerald-500 focus:ring-2 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-50 dark:placeholder-gray-500"
84
+ />
85
+ </div>
86
+ <div className="space-y-2">
87
+ <label htmlFor="password" className="text-sm font-medium text-gray-900 dark:text-gray-50">Password</label>
88
+ <input
89
+ id="password"
90
+ type="password"
91
+ value={password}
92
+ onChange={(e) => setPassword(e.target.value)}
93
+ required
94
+ placeholder="Enter your password"
95
+ autoComplete="current-password"
96
+ className="w-full rounded-md border border-gray-200 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 outline-none ring-emerald-500 transition focus:border-emerald-500 focus:ring-2 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-50 dark:placeholder-gray-500"
97
+ />
98
+ </div>
99
+ <button
100
+ type="submit"
101
+ className="w-full rounded-md bg-emerald-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-emerald-700 disabled:opacity-50"
102
+ disabled={loading}
103
+ >
104
+ {loading ? 'Signing in...' : 'Sign in'}
105
+ </button>
106
+ </form>
107
+
108
+ <p className="mt-6 text-center text-sm text-gray-500 dark:text-gray-400">
109
+ Don't have an account?{' '}
110
+ <button
111
+ type="button"
112
+ className="font-medium text-gray-900 underline underline-offset-4 dark:text-gray-50"
113
+ onClick={() => navigate('/register')}
114
+ >
115
+ Register
116
+ </button>
117
+ </p>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ )
122
+ }
@@ -0,0 +1,137 @@
1
+ import { useState } from 'react'
2
+ import { post } from '../lib/api.ts'
3
+
4
+ interface RegisterProps {
5
+ appName?: string
6
+ navigate: (href: string) => void
7
+ [key: string]: any
8
+ }
9
+
10
+ export default function Register({ appName = 'Mantiq', navigate }: RegisterProps) {
11
+ const [name, setName] = useState('')
12
+ const [email, setEmail] = useState('')
13
+ const [password, setPassword] = useState('')
14
+ const [error, setError] = useState('')
15
+ const [loading, setLoading] = useState(false)
16
+
17
+ const handleSubmit = async (e: React.FormEvent) => {
18
+ e.preventDefault()
19
+ setError('')
20
+ setLoading(true)
21
+ const { ok, data } = await post('/register', { name, email, password })
22
+ if (ok) navigate('/dashboard')
23
+ else setError(data?.error?.message ?? data?.error ?? 'Registration failed. Please try again.')
24
+ setLoading(false)
25
+ }
26
+
27
+ return (
28
+ <div className="flex min-h-screen bg-white dark:bg-gray-950">
29
+ {/* Left brand panel */}
30
+ <div className="hidden lg:flex lg:w-[45%] flex-col justify-between bg-gray-900 p-10 text-white dark:bg-gray-900">
31
+ <div className="flex items-center gap-3">
32
+ <div className="flex h-8 w-8 items-center justify-center rounded bg-white text-xs font-bold text-gray-900">
33
+ M
34
+ </div>
35
+ <span className="text-lg font-semibold tracking-tight">{appName}</span>
36
+ </div>
37
+
38
+ <div>
39
+ <blockquote className="text-2xl font-medium leading-snug tracking-tight">
40
+ "The framework that gets out of your way."
41
+ </blockquote>
42
+ </div>
43
+
44
+ <p className="text-sm text-white/50">
45
+ &copy; {new Date().getFullYear()} {appName}. All rights reserved.
46
+ </p>
47
+ </div>
48
+
49
+ {/* Right form panel */}
50
+ <div className="flex flex-1 flex-col items-center justify-center px-6 py-12">
51
+ {/* Mobile-only logo */}
52
+ <div className="mb-10 flex items-center gap-3 lg:hidden">
53
+ <div className="flex h-8 w-8 items-center justify-center rounded bg-gray-900 text-xs font-bold text-white dark:bg-gray-50 dark:text-gray-900">
54
+ M
55
+ </div>
56
+ <span className="text-lg font-semibold tracking-tight text-gray-900 dark:text-gray-50">{appName}</span>
57
+ </div>
58
+
59
+ <div className="w-full max-w-sm">
60
+ <div className="mb-8">
61
+ <h1 className="text-2xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">Create an account</h1>
62
+ <p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
63
+ Get started with {appName} today
64
+ </p>
65
+ </div>
66
+
67
+ {error && (
68
+ <div className="mb-6 rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600 dark:border-red-800 dark:bg-red-950 dark:text-red-400">
69
+ {error}
70
+ </div>
71
+ )}
72
+
73
+ <form onSubmit={handleSubmit} className="space-y-4">
74
+ <div className="space-y-2">
75
+ <label htmlFor="name" className="text-sm font-medium text-gray-900 dark:text-gray-50">Name</label>
76
+ <input
77
+ id="name"
78
+ value={name}
79
+ onChange={(e) => setName(e.target.value)}
80
+ required
81
+ placeholder="John Doe"
82
+ autoComplete="name"
83
+ className="w-full rounded-md border border-gray-200 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 outline-none ring-emerald-500 transition focus:border-emerald-500 focus:ring-2 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-50 dark:placeholder-gray-500"
84
+ />
85
+ </div>
86
+ <div className="space-y-2">
87
+ <label htmlFor="email" className="text-sm font-medium text-gray-900 dark:text-gray-50">Email</label>
88
+ <input
89
+ id="email"
90
+ type="email"
91
+ value={email}
92
+ onChange={(e) => setEmail(e.target.value)}
93
+ required
94
+ placeholder="you@example.com"
95
+ autoComplete="email"
96
+ className="w-full rounded-md border border-gray-200 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 outline-none ring-emerald-500 transition focus:border-emerald-500 focus:ring-2 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-50 dark:placeholder-gray-500"
97
+ />
98
+ </div>
99
+ <div className="space-y-2">
100
+ <label htmlFor="password" className="text-sm font-medium text-gray-900 dark:text-gray-50">Password</label>
101
+ <input
102
+ id="password"
103
+ type="password"
104
+ value={password}
105
+ onChange={(e) => setPassword(e.target.value)}
106
+ required
107
+ placeholder="Create a strong password"
108
+ autoComplete="new-password"
109
+ minLength={8}
110
+ className="w-full rounded-md border border-gray-200 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 outline-none ring-emerald-500 transition focus:border-emerald-500 focus:ring-2 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-50 dark:placeholder-gray-500"
111
+ />
112
+ <p className="text-xs text-gray-500 dark:text-gray-400">Must be at least 8 characters</p>
113
+ </div>
114
+ <button
115
+ type="submit"
116
+ className="w-full rounded-md bg-emerald-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-emerald-700 disabled:opacity-50"
117
+ disabled={loading}
118
+ >
119
+ {loading ? 'Creating account...' : 'Create account'}
120
+ </button>
121
+ </form>
122
+
123
+ <p className="mt-6 text-center text-sm text-gray-500 dark:text-gray-400">
124
+ Already have an account?{' '}
125
+ <button
126
+ type="button"
127
+ className="font-medium text-gray-900 underline underline-offset-4 dark:text-gray-50"
128
+ onClick={() => navigate('/login')}
129
+ >
130
+ Sign in
131
+ </button>
132
+ </p>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ )
137
+ }