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,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,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
|
+
© {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
|
+
© {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>
|