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.
- package/package.json +2 -1
- package/skeleton/.env.example +64 -0
- package/skeleton/README.md +46 -0
- package/skeleton/app/Console/Commands/.gitkeep +0 -0
- package/skeleton/app/Enums/UserStatus.ts +7 -0
- package/skeleton/app/Http/Controllers/HomeController.ts +78 -0
- package/skeleton/app/Http/Middleware/.gitkeep +0 -0
- package/skeleton/app/Models/User.ts +7 -0
- package/skeleton/app/Providers/AppServiceProvider.ts +25 -0
- package/skeleton/app/Providers/DatabaseServiceProvider.ts +17 -0
- package/skeleton/bootstrap/.gitkeep +0 -0
- package/skeleton/config/ai.ts +51 -0
- package/skeleton/config/app.ts +108 -0
- package/skeleton/config/auth.ts +51 -0
- package/skeleton/config/broadcasting.ts +93 -0
- package/skeleton/config/cache.ts +61 -0
- package/skeleton/config/cors.ts +77 -0
- package/skeleton/config/database.ts +120 -0
- package/skeleton/config/filesystem.ts +58 -0
- package/skeleton/config/hashing.ts +47 -0
- package/skeleton/config/heartbeat.ts +112 -0
- package/skeleton/config/logging.ts +58 -0
- package/skeleton/config/mail.ts +93 -0
- package/skeleton/config/notify.ts +141 -0
- package/skeleton/config/queue.ts +59 -0
- package/skeleton/config/search.ts +96 -0
- package/skeleton/config/services.ts +110 -0
- package/skeleton/config/session.ts +84 -0
- package/skeleton/config/vite.ts +33 -0
- package/skeleton/database/factories/.gitkeep +0 -0
- package/skeleton/database/migrations/001_create_users_table.ts +19 -0
- package/skeleton/database/migrations/002_create_personal_access_tokens_table.ts +22 -0
- package/skeleton/database/seeders/DatabaseSeeder.ts +7 -0
- package/skeleton/index.ts +20 -0
- package/skeleton/mantiq.ts +8 -0
- package/skeleton/package.json +34 -0
- package/skeleton/public/.gitkeep +0 -0
- package/skeleton/routes/api.ts +8 -0
- package/skeleton/routes/channels.ts +23 -0
- package/skeleton/routes/console.ts +24 -0
- package/skeleton/routes/web.ts +6 -0
- package/skeleton/storage/cache/.gitkeep +0 -0
- package/skeleton/storage/framework/.gitkeep +0 -0
- package/skeleton/tests/feature/api.test.ts +14 -0
- package/skeleton/tests/feature/home.test.ts +17 -0
- package/skeleton/tests/unit/example.test.ts +11 -0
- package/skeleton/tsconfig.json +27 -0
- package/src/index.ts +289 -25
- package/src/templates.ts +141 -945
- package/src/terminal.ts +64 -0
- package/stubs/api-only/routes/api.ts.stub +24 -0
- package/stubs/api-only/tests/feature/token-auth.test.ts.stub +69 -0
- package/stubs/auth/api/app/Http/Controllers/ApiAuthController.ts.stub +57 -0
- package/stubs/auth/api/routes/api.ts.stub +24 -0
- package/stubs/auth/api/tests/feature/token-auth.test.ts.stub +69 -0
- package/stubs/auth/shared/app/Http/Requests/LoginRequest.ts.stub +10 -0
- package/stubs/auth/shared/app/Http/Requests/RegisterRequest.ts.stub +11 -0
- package/stubs/auth/web/app/Http/Controllers/AuthController.ts.stub +43 -0
- package/stubs/auth/web/app/Http/Controllers/PageController.ts.stub +66 -0
- package/stubs/auth/web/routes/web.ts.stub +25 -0
- package/stubs/auth/web/svelte/src/App.svelte.stub +77 -0
- package/stubs/auth/web/svelte/src/pages.ts.stub +17 -0
- package/stubs/auth/web/tests/feature/auth.test.ts.stub +69 -0
- package/stubs/auth/web/vue/src/App.vue.stub +74 -0
- package/stubs/auth/web/vue/src/pages.ts.stub +17 -0
- package/stubs/manifest.json +630 -2
- package/stubs/noauth/app/Http/Controllers/PageController.ts.stub +41 -0
- package/stubs/noauth/app/Models/User.ts.stub +5 -0
- package/stubs/noauth/database/migrations/001_create_users_table.ts.stub +17 -0
- package/stubs/noauth/routes/api.ts.stub +16 -0
- package/stubs/noauth/routes/web.ts.stub +15 -0
- package/stubs/noauth/svelte/src/App.svelte.stub +68 -0
- package/stubs/noauth/svelte/src/pages.ts.stub +7 -0
- package/stubs/noauth/vue/src/App.vue.stub +62 -0
- package/stubs/noauth/vue/src/pages.ts.stub +7 -0
- package/stubs/react/src/App.tsx.stub +4 -2
- package/stubs/react/src/components/layout/search-dialog.tsx.stub +2 -2
- package/stubs/react/src/components/layout/sidebar-data.ts.stub +2 -2
- package/stubs/react/src/lib/api.ts.stub +30 -6
- package/stubs/react/src/pages/Login.tsx.stub +3 -3
- package/stubs/react/src/pages/users/dialogs.tsx.stub +7 -26
- package/stubs/react/vite.config.ts.stub +26 -3
- package/stubs/shared/app/Http/Controllers/ApiAuthController.ts.stub +57 -0
- package/stubs/shared/app/Http/Controllers/AuthController.ts.stub +14 -38
- package/stubs/shared/app/Http/Controllers/PageController.ts.stub +3 -3
- package/stubs/shared/app/Http/Controllers/UserController.ts.stub +61 -0
- package/stubs/shared/app/Http/Requests/LoginRequest.ts.stub +10 -0
- package/stubs/shared/app/Http/Requests/RegisterRequest.ts.stub +11 -0
- package/stubs/shared/app/Http/Requests/StoreUserRequest.ts.stub +11 -0
- package/stubs/shared/app/Http/Requests/UpdateUserRequest.ts.stub +11 -0
- package/stubs/shared/config/app.ts.stub +36 -0
- package/stubs/shared/config/vite.ts.stub +8 -0
- package/stubs/shared/database/factories/UserFactory.ts.stub +4 -6
- package/stubs/shared/routes/api.ts.stub +12 -102
- package/stubs/shared/routes/web.ts.stub +5 -3
- package/stubs/shared/tests/feature/auth.test.ts.stub +69 -0
- package/stubs/shared/tests/feature/users.test.ts.stub +90 -0
- package/stubs/svelte/src/App.svelte.stub +1 -1
- package/stubs/svelte/src/lib/api.ts.stub +30 -6
- package/stubs/svelte/src/main.ts.stub +3 -1
- package/stubs/svelte/src/pages/Login.svelte.stub +3 -3
- package/stubs/svelte/vite.config.ts.stub +20 -1
- package/stubs/tailwind-only/react/src/components/layout/app-sidebar.tsx.stub +68 -0
- package/stubs/tailwind-only/react/src/components/layout/authenticated-layout.tsx.stub +57 -0
- package/stubs/tailwind-only/react/src/components/layout/header.tsx.stub +52 -0
- package/stubs/tailwind-only/react/src/components/layout/main.tsx.stub +21 -0
- package/stubs/tailwind-only/react/src/components/layout/nav-group.tsx.stub +185 -0
- package/stubs/tailwind-only/react/src/components/layout/nav-user.tsx.stub +106 -0
- package/stubs/tailwind-only/react/src/components/layout/sidebar-data.ts.stub +58 -0
- package/stubs/tailwind-only/react/src/components/layout/theme-toggle.tsx.stub +36 -0
- package/stubs/tailwind-only/react/src/components/layout/top-nav.tsx.stub +72 -0
- package/stubs/tailwind-only/react/src/lib/utils.ts.stub +6 -0
- package/stubs/tailwind-only/react/src/pages/Dashboard.tsx.stub +205 -0
- package/stubs/tailwind-only/react/src/pages/Login.tsx.stub +122 -0
- package/stubs/tailwind-only/react/src/pages/Register.tsx.stub +137 -0
- package/stubs/tailwind-only/react/src/pages/Users.tsx.stub +426 -0
- package/stubs/tailwind-only/react/src/pages/account/layout.tsx.stub +64 -0
- package/stubs/tailwind-only/react/src/pages/account/preferences.tsx.stub +80 -0
- package/stubs/tailwind-only/react/src/pages/account/profile.tsx.stub +67 -0
- package/stubs/tailwind-only/react/src/pages/account/security.tsx.stub +91 -0
- package/stubs/tailwind-only/react/src/style.css.stub +14 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/app-sidebar.svelte.stub +104 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/authenticated-layout.svelte.stub +51 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/header.svelte.stub +66 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/main.svelte.stub +26 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/nav-group.svelte.stub +131 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/nav-user.svelte.stub +104 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/sidebar-data.ts.stub +57 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/theme-toggle.svelte.stub +28 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/top-nav.svelte.stub +99 -0
- package/stubs/tailwind-only/svelte/src/lib/utils.ts.stub +6 -0
- package/stubs/tailwind-only/svelte/src/pages/Dashboard.svelte.stub +192 -0
- package/stubs/tailwind-only/svelte/src/pages/Login.svelte.stub +120 -0
- package/stubs/tailwind-only/svelte/src/pages/Register.svelte.stub +134 -0
- package/stubs/tailwind-only/svelte/src/pages/Users.svelte.stub +293 -0
- package/stubs/tailwind-only/svelte/src/pages/account/Layout.svelte.stub +61 -0
- package/stubs/tailwind-only/svelte/src/pages/account/Preferences.svelte.stub +81 -0
- package/stubs/tailwind-only/svelte/src/pages/account/Profile.svelte.stub +76 -0
- package/stubs/tailwind-only/svelte/src/pages/account/Security.svelte.stub +140 -0
- package/stubs/tailwind-only/svelte/src/style.css.stub +127 -0
- package/stubs/tailwind-only/vue/src/components/layout/AppSidebar.vue.stub +60 -0
- package/stubs/tailwind-only/vue/src/components/layout/AuthenticatedLayout.vue.stub +73 -0
- package/stubs/tailwind-only/vue/src/components/layout/Header.vue.stub +54 -0
- package/stubs/tailwind-only/vue/src/components/layout/Main.vue.stub +22 -0
- package/stubs/tailwind-only/vue/src/components/layout/NavGroup.vue.stub +107 -0
- package/stubs/tailwind-only/vue/src/components/layout/NavUser.vue.stub +104 -0
- package/stubs/tailwind-only/vue/src/components/layout/ThemeToggle.vue.stub +28 -0
- package/stubs/tailwind-only/vue/src/components/layout/TopNav.vue.stub +86 -0
- package/stubs/tailwind-only/vue/src/components/layout/sidebar-data.ts.stub +57 -0
- package/stubs/tailwind-only/vue/src/lib/utils.ts.stub +7 -0
- package/stubs/tailwind-only/vue/src/pages/Dashboard.vue.stub +195 -0
- package/stubs/tailwind-only/vue/src/pages/Login.vue.stub +121 -0
- package/stubs/tailwind-only/vue/src/pages/Register.vue.stub +137 -0
- package/stubs/tailwind-only/vue/src/pages/Users.vue.stub +401 -0
- package/stubs/tailwind-only/vue/src/pages/account/Layout.vue.stub +56 -0
- package/stubs/tailwind-only/vue/src/pages/account/Preferences.vue.stub +81 -0
- package/stubs/tailwind-only/vue/src/pages/account/Profile.vue.stub +69 -0
- package/stubs/tailwind-only/vue/src/pages/account/Security.vue.stub +85 -0
- package/stubs/tailwind-only/vue/src/style.css.stub +26 -0
- package/stubs/themes/corporate/react/src/components/layout/app-sidebar.tsx.stub +74 -0
- package/stubs/themes/corporate/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
- package/stubs/themes/corporate/react/src/pages/Dashboard.tsx.stub +310 -0
- package/stubs/themes/corporate/react/src/pages/Login.tsx.stub +122 -0
- package/stubs/themes/corporate/react/src/pages/Register.tsx.stub +144 -0
- package/stubs/themes/corporate/react/src/style.css.stub +135 -0
- package/stubs/themes/corporate/svelte/src/pages/Dashboard.svelte.stub +271 -0
- package/stubs/themes/corporate/svelte/src/pages/Login.svelte.stub +117 -0
- package/stubs/themes/corporate/svelte/src/pages/Register.svelte.stub +138 -0
- package/stubs/themes/corporate/svelte/src/style.css.stub +134 -0
- package/stubs/themes/corporate/vue/src/pages/Dashboard.vue.stub +325 -0
- package/stubs/themes/corporate/vue/src/pages/Login.vue.stub +118 -0
- package/stubs/themes/corporate/vue/src/pages/Register.vue.stub +139 -0
- package/stubs/themes/corporate/vue/src/style.css.stub +134 -0
- package/stubs/themes/minimal/react/src/components/layout/app-sidebar.tsx.stub +68 -0
- package/stubs/themes/minimal/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
- package/stubs/themes/minimal/react/src/pages/Dashboard.tsx.stub +166 -0
- package/stubs/themes/minimal/react/src/pages/Login.tsx.stub +95 -0
- package/stubs/themes/minimal/react/src/pages/Register.tsx.stub +73 -0
- package/stubs/themes/minimal/react/src/style.css.stub +142 -0
- package/stubs/themes/minimal/svelte/src/pages/Dashboard.svelte.stub +149 -0
- package/stubs/themes/minimal/svelte/src/pages/Login.svelte.stub +90 -0
- package/stubs/themes/minimal/svelte/src/pages/Register.svelte.stub +70 -0
- package/stubs/themes/minimal/svelte/src/style.css.stub +142 -0
- package/stubs/themes/minimal/vue/src/pages/Dashboard.vue.stub +163 -0
- package/stubs/themes/minimal/vue/src/pages/Login.vue.stub +91 -0
- package/stubs/themes/minimal/vue/src/pages/Register.vue.stub +73 -0
- package/stubs/themes/minimal/vue/src/style.css.stub +142 -0
- package/stubs/themes/starter/react/src/components/layout/app-sidebar.tsx.stub +74 -0
- package/stubs/themes/starter/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
- package/stubs/themes/starter/react/src/pages/Dashboard.tsx.stub +236 -0
- package/stubs/themes/starter/react/src/pages/Login.tsx.stub +131 -0
- package/stubs/themes/starter/react/src/pages/Register.tsx.stub +145 -0
- package/stubs/themes/starter/react/src/style.css.stub +141 -0
- package/stubs/themes/starter/svelte/src/pages/Dashboard.svelte.stub +212 -0
- package/stubs/themes/starter/svelte/src/pages/Login.svelte.stub +126 -0
- package/stubs/themes/starter/svelte/src/pages/Register.svelte.stub +139 -0
- package/stubs/themes/starter/svelte/src/style.css.stub +141 -0
- package/stubs/themes/starter/vue/src/pages/Dashboard.vue.stub +228 -0
- package/stubs/themes/starter/vue/src/pages/Login.vue.stub +127 -0
- package/stubs/themes/starter/vue/src/pages/Register.vue.stub +140 -0
- package/stubs/themes/starter/vue/src/style.css.stub +141 -0
- package/stubs/themes/workspace/react/src/components/layout/app-sidebar.tsx.stub +97 -0
- package/stubs/themes/workspace/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
- package/stubs/themes/workspace/react/src/pages/Dashboard.tsx.stub +304 -0
- package/stubs/themes/workspace/react/src/pages/Login.tsx.stub +131 -0
- package/stubs/themes/workspace/react/src/pages/Register.tsx.stub +82 -0
- package/stubs/themes/workspace/react/src/style.css.stub +138 -0
- package/stubs/themes/workspace/svelte/src/pages/Dashboard.svelte.stub +215 -0
- package/stubs/themes/workspace/svelte/src/pages/Login.svelte.stub +124 -0
- package/stubs/themes/workspace/svelte/src/pages/Register.svelte.stub +76 -0
- package/stubs/themes/workspace/svelte/src/style.css.stub +134 -0
- package/stubs/themes/workspace/vue/src/pages/Dashboard.vue.stub +220 -0
- package/stubs/themes/workspace/vue/src/pages/Login.vue.stub +128 -0
- package/stubs/themes/workspace/vue/src/pages/Register.vue.stub +80 -0
- package/stubs/themes/workspace/vue/src/style.css.stub +134 -0
- package/stubs/vue/src/App.vue.stub +2 -1
- package/stubs/vue/src/lib/api.ts.stub +30 -6
- package/stubs/vue/src/main.ts.stub +3 -1
- package/stubs/vue/src/pages/Login.vue.stub +3 -3
- 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
|
+
© {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
|
+
© {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
|
+
}
|