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,138 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { post } from '$lib/api'
|
|
3
|
+
import { Button } from '$lib/components/ui/button'
|
|
4
|
+
import { Input } from '$lib/components/ui/input'
|
|
5
|
+
import { Label } from '$lib/components/ui/label'
|
|
6
|
+
import { Separator } from '$lib/components/ui/separator'
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
appName?: string
|
|
10
|
+
navigate?: (href: string) => void
|
|
11
|
+
}
|
|
12
|
+
let { appName = 'Mantiq', navigate = () => {} }: Props = $props()
|
|
13
|
+
|
|
14
|
+
let name = $state('')
|
|
15
|
+
let email = $state('')
|
|
16
|
+
let password = $state('')
|
|
17
|
+
let error = $state('')
|
|
18
|
+
let loading = $state(false)
|
|
19
|
+
|
|
20
|
+
async function handleSubmit(e: SubmitEvent) {
|
|
21
|
+
e.preventDefault()
|
|
22
|
+
error = ''
|
|
23
|
+
loading = true
|
|
24
|
+
const { ok, data } = await post('/register', { name, email, password })
|
|
25
|
+
if (ok) navigate('/dashboard')
|
|
26
|
+
else error = data?.error?.message ?? data?.error ?? 'Registration failed. Please try again.'
|
|
27
|
+
loading = false
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<div class="min-h-screen bg-muted/50 flex items-center justify-center p-4">
|
|
32
|
+
<div class="w-full max-w-sm">
|
|
33
|
+
<!-- Logo -->
|
|
34
|
+
<div class="mb-8 flex flex-col items-center gap-3">
|
|
35
|
+
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-primary text-primary-foreground text-sm font-bold shadow-sm">
|
|
36
|
+
M
|
|
37
|
+
</div>
|
|
38
|
+
<span class="text-lg font-semibold tracking-tight">{appName}</span>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<!-- Card -->
|
|
42
|
+
<div class="rounded-lg border bg-card shadow-sm p-6">
|
|
43
|
+
<div class="mb-6">
|
|
44
|
+
<h1 class="text-xl font-semibold tracking-tight">Create an account</h1>
|
|
45
|
+
<p class="mt-1 text-sm text-muted-foreground">
|
|
46
|
+
Get started with {appName} today
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
{#if error}
|
|
51
|
+
<div class="mb-4 rounded-md border border-destructive px-4 py-3 text-sm text-destructive">
|
|
52
|
+
{error}
|
|
53
|
+
</div>
|
|
54
|
+
{/if}
|
|
55
|
+
|
|
56
|
+
<form onsubmit={handleSubmit} class="space-y-4">
|
|
57
|
+
<div class="space-y-2">
|
|
58
|
+
<Label for="name">Name</Label>
|
|
59
|
+
<Input
|
|
60
|
+
id="name"
|
|
61
|
+
bind:value={name}
|
|
62
|
+
required
|
|
63
|
+
placeholder="John Doe"
|
|
64
|
+
autocomplete="name"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="space-y-2">
|
|
68
|
+
<Label for="email">Email</Label>
|
|
69
|
+
<Input
|
|
70
|
+
id="email"
|
|
71
|
+
type="email"
|
|
72
|
+
bind:value={email}
|
|
73
|
+
required
|
|
74
|
+
placeholder="you@example.com"
|
|
75
|
+
autocomplete="email"
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="space-y-2">
|
|
79
|
+
<Label for="password">Password</Label>
|
|
80
|
+
<Input
|
|
81
|
+
id="password"
|
|
82
|
+
type="password"
|
|
83
|
+
bind:value={password}
|
|
84
|
+
required
|
|
85
|
+
placeholder="Create a strong password"
|
|
86
|
+
autocomplete="new-password"
|
|
87
|
+
minlength={8}
|
|
88
|
+
/>
|
|
89
|
+
<p class="text-xs text-muted-foreground">Must be at least 8 characters</p>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div class="flex items-start gap-2">
|
|
93
|
+
<input
|
|
94
|
+
type="checkbox"
|
|
95
|
+
id="terms"
|
|
96
|
+
class="mt-1 h-3.5 w-3.5 rounded border-input accent-primary"
|
|
97
|
+
required
|
|
98
|
+
/>
|
|
99
|
+
<label for="terms" class="text-xs text-muted-foreground leading-relaxed">
|
|
100
|
+
I agree to the{' '}
|
|
101
|
+
<button type="button" class="font-medium text-primary hover:text-primary/80">Terms of Service</button>
|
|
102
|
+
{' '}and{' '}
|
|
103
|
+
<button type="button" class="font-medium text-primary hover:text-primary/80">Privacy Policy</button>
|
|
104
|
+
</label>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<Button type="submit" class="w-full" disabled={loading}>
|
|
108
|
+
{loading ? 'Creating account...' : 'Create account'}
|
|
109
|
+
</Button>
|
|
110
|
+
</form>
|
|
111
|
+
|
|
112
|
+
<div class="relative my-6">
|
|
113
|
+
<div class="absolute inset-0 flex items-center">
|
|
114
|
+
<Separator class="w-full" />
|
|
115
|
+
</div>
|
|
116
|
+
<div class="relative flex justify-center text-xs uppercase">
|
|
117
|
+
<span class="bg-card px-2 text-muted-foreground">or</span>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<p class="text-center text-sm text-muted-foreground">
|
|
122
|
+
Already have an account?{' '}
|
|
123
|
+
<button
|
|
124
|
+
type="button"
|
|
125
|
+
class="font-medium text-primary hover:text-primary/80"
|
|
126
|
+
onclick={() => navigate('/login')}
|
|
127
|
+
>
|
|
128
|
+
Sign in
|
|
129
|
+
</button>
|
|
130
|
+
</p>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<!-- Footer -->
|
|
134
|
+
<p class="mt-6 text-center text-xs text-muted-foreground">
|
|
135
|
+
Powered by {appName}
|
|
136
|
+
</p>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* shadcn/ui theme — Corporate
|
|
6
|
+
*
|
|
7
|
+
* Clean zinc neutrals with indigo/violet accent.
|
|
8
|
+
* Inspired by Corporate and Tailwind's professional design language.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
@theme inline {
|
|
12
|
+
--color-background: var(--background);
|
|
13
|
+
--color-foreground: var(--foreground);
|
|
14
|
+
--color-card: var(--card);
|
|
15
|
+
--color-card-foreground: var(--card-foreground);
|
|
16
|
+
--color-popover: var(--popover);
|
|
17
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
18
|
+
--color-primary: var(--primary);
|
|
19
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
20
|
+
--color-secondary: var(--secondary);
|
|
21
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
22
|
+
--color-muted: var(--muted);
|
|
23
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
24
|
+
--color-accent: var(--accent);
|
|
25
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
26
|
+
--color-destructive: var(--destructive);
|
|
27
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
28
|
+
--color-border: var(--border);
|
|
29
|
+
--color-input: var(--input);
|
|
30
|
+
--color-ring: var(--ring);
|
|
31
|
+
--color-chart-1: var(--chart-1);
|
|
32
|
+
--color-chart-2: var(--chart-2);
|
|
33
|
+
--color-chart-3: var(--chart-3);
|
|
34
|
+
--color-chart-4: var(--chart-4);
|
|
35
|
+
--color-chart-5: var(--chart-5);
|
|
36
|
+
--color-sidebar: var(--sidebar);
|
|
37
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
38
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
39
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
40
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
41
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
42
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
43
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
44
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
45
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
46
|
+
--radius-lg: var(--radius);
|
|
47
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
:root {
|
|
51
|
+
--radius: 0.625rem;
|
|
52
|
+
--background: oklch(0.985 0.002 247.858);
|
|
53
|
+
--foreground: oklch(0.141 0.005 285.823);
|
|
54
|
+
--card: oklch(1 0 0);
|
|
55
|
+
--card-foreground: oklch(0.141 0.005 285.823);
|
|
56
|
+
--popover: oklch(1 0 0);
|
|
57
|
+
--popover-foreground: oklch(0.141 0.005 285.823);
|
|
58
|
+
--primary: oklch(0.585 0.233 277.117);
|
|
59
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
60
|
+
--secondary: oklch(0.967 0.001 286.375);
|
|
61
|
+
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
62
|
+
--muted: oklch(0.967 0.001 286.375);
|
|
63
|
+
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
64
|
+
--accent: oklch(0.967 0.001 286.375);
|
|
65
|
+
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
66
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
67
|
+
--destructive-foreground: oklch(0.577 0.245 27.325);
|
|
68
|
+
--border: oklch(0.922 0.004 286.32);
|
|
69
|
+
--input: oklch(0.922 0.004 286.32);
|
|
70
|
+
--ring: oklch(0.585 0.233 277.117);
|
|
71
|
+
--chart-1: oklch(0.585 0.233 277.117);
|
|
72
|
+
--chart-2: oklch(0.553 0.213 264.364);
|
|
73
|
+
--chart-3: oklch(0.496 0.265 301.924);
|
|
74
|
+
--chart-4: oklch(0.6 0.118 184.714);
|
|
75
|
+
--chart-5: oklch(0.828 0.189 84.429);
|
|
76
|
+
--sidebar: oklch(0.985 0.002 247.858);
|
|
77
|
+
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
|
78
|
+
--sidebar-primary: oklch(0.585 0.233 277.117);
|
|
79
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
80
|
+
--sidebar-accent: oklch(0.967 0.001 286.375);
|
|
81
|
+
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
|
82
|
+
--sidebar-border: oklch(0.922 0.004 286.32);
|
|
83
|
+
--sidebar-ring: oklch(0.585 0.233 277.117);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.dark {
|
|
87
|
+
--background: oklch(0.141 0.005 285.823);
|
|
88
|
+
--foreground: oklch(0.985 0 0);
|
|
89
|
+
--card: oklch(0.178 0.005 285.823);
|
|
90
|
+
--card-foreground: oklch(0.985 0 0);
|
|
91
|
+
--popover: oklch(0.178 0.005 285.823);
|
|
92
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
93
|
+
--primary: oklch(0.65 0.21 277.117);
|
|
94
|
+
--primary-foreground: oklch(0.145 0.004 285.823);
|
|
95
|
+
--secondary: oklch(0.269 0.006 286.033);
|
|
96
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
97
|
+
--muted: oklch(0.269 0.006 286.033);
|
|
98
|
+
--muted-foreground: oklch(0.708 0.014 286.067);
|
|
99
|
+
--accent: oklch(0.269 0.006 286.033);
|
|
100
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
101
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
102
|
+
--destructive-foreground: oklch(0.704 0.191 22.216);
|
|
103
|
+
--border: oklch(0.274 0.006 286.033);
|
|
104
|
+
--input: oklch(0.274 0.006 286.033);
|
|
105
|
+
--ring: oklch(0.65 0.21 277.117);
|
|
106
|
+
--chart-1: oklch(0.65 0.21 277.117);
|
|
107
|
+
--chart-2: oklch(0.623 0.214 262.881);
|
|
108
|
+
--chart-3: oklch(0.627 0.265 303.9);
|
|
109
|
+
--chart-4: oklch(0.696 0.17 162.48);
|
|
110
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
111
|
+
--sidebar: oklch(0.16 0.005 285.823);
|
|
112
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
113
|
+
--sidebar-primary: oklch(0.65 0.21 277.117);
|
|
114
|
+
--sidebar-primary-foreground: oklch(0.145 0.004 285.823);
|
|
115
|
+
--sidebar-accent: oklch(0.269 0.006 286.033);
|
|
116
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
117
|
+
--sidebar-border: oklch(0.274 0.006 286.033);
|
|
118
|
+
--sidebar-ring: oklch(0.65 0.21 277.117);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@layer base {
|
|
122
|
+
* {
|
|
123
|
+
@apply border-border;
|
|
124
|
+
}
|
|
125
|
+
body {
|
|
126
|
+
@apply bg-background text-foreground;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@keyframes fadeUp {
|
|
131
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
132
|
+
to { opacity: 1; transform: translateY(0); }
|
|
133
|
+
}
|
|
134
|
+
.animate-fade-up { animation: fadeUp 0.4s ease-out; }
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import AuthenticatedLayout from '@/components/layout/AuthenticatedLayout.vue'
|
|
4
|
+
import Header from '@/components/layout/Header.vue'
|
|
5
|
+
import Main from '@/components/layout/Main.vue'
|
|
6
|
+
import TopNav from '@/components/layout/TopNav.vue'
|
|
7
|
+
import { Button } from '@/components/ui/button'
|
|
8
|
+
import {
|
|
9
|
+
Card,
|
|
10
|
+
CardContent,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
CardDescription,
|
|
14
|
+
} from '@/components/ui/card'
|
|
15
|
+
import { Badge } from '@/components/ui/badge'
|
|
16
|
+
import { Separator } from '@/components/ui/separator'
|
|
17
|
+
import {
|
|
18
|
+
Table,
|
|
19
|
+
TableBody,
|
|
20
|
+
TableCell,
|
|
21
|
+
TableHead,
|
|
22
|
+
TableHeader,
|
|
23
|
+
TableRow,
|
|
24
|
+
} from '@/components/ui/table'
|
|
25
|
+
import {
|
|
26
|
+
TrendingUp,
|
|
27
|
+
ArrowUpRight,
|
|
28
|
+
ArrowDownRight,
|
|
29
|
+
CreditCard,
|
|
30
|
+
DollarSign,
|
|
31
|
+
Users,
|
|
32
|
+
Activity,
|
|
33
|
+
} from 'lucide-vue-next'
|
|
34
|
+
|
|
35
|
+
const props = withDefaults(defineProps<{
|
|
36
|
+
appName?: string
|
|
37
|
+
currentUser?: { id: number; name: string; email: string; role: string } | null
|
|
38
|
+
navigate?: (href: string) => void
|
|
39
|
+
}>(), {
|
|
40
|
+
appName: 'Mantiq',
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const topNav = [
|
|
44
|
+
{ title: 'Overview', href: '/dashboard', isActive: true },
|
|
45
|
+
{ title: 'Sales', href: '/dashboard', isActive: false, disabled: true },
|
|
46
|
+
{ title: 'Tickets', href: '/dashboard', isActive: false, disabled: true },
|
|
47
|
+
{ title: 'Performance', href: '/dashboard', isActive: false, disabled: true },
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
const transactions = [
|
|
51
|
+
{ customer: 'Olivia Martin', email: 'olivia@email.com', amount: '$1,999.00', status: 'Completed', date: 'Mar 15, 2025' },
|
|
52
|
+
{ customer: 'Jackson Lee', email: 'jackson@email.com', amount: '$39.00', status: 'Completed', date: 'Mar 14, 2025' },
|
|
53
|
+
{ customer: 'Isabella Nguyen', email: 'isabella@email.com', amount: '$299.00', status: 'Pending', date: 'Mar 14, 2025' },
|
|
54
|
+
{ customer: 'William Kim', email: 'will@email.com', amount: '$99.00', status: 'Failed', date: 'Mar 13, 2025' },
|
|
55
|
+
{ customer: 'Sofia Davis', email: 'sofia@email.com', amount: '$39.00', status: 'Completed', date: 'Mar 13, 2025' },
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
function statusColor(status: string) {
|
|
59
|
+
if (status === 'Completed') return 'bg-emerald-500/15 text-emerald-700 dark:text-emerald-400 border-emerald-500/20'
|
|
60
|
+
if (status === 'Pending') return 'bg-amber-500/15 text-amber-700 dark:text-amber-400 border-amber-500/20'
|
|
61
|
+
return 'bg-red-500/15 text-red-700 dark:text-red-400 border-red-500/20'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const activeRange = ref('7d')
|
|
65
|
+
|
|
66
|
+
const ranges = ['7d', '30d', '90d'] as const
|
|
67
|
+
|
|
68
|
+
function rangeLabel(range: string) {
|
|
69
|
+
if (range === '7d') return '7 days'
|
|
70
|
+
if (range === '30d') return '30 days'
|
|
71
|
+
return '90 days'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Revenue chart data
|
|
75
|
+
const chartData = [1200, 2100, 1800, 3200, 2800, 4100, 3600, 5200, 4800, 5800, 5100, 6200, 5600]
|
|
76
|
+
const maxVal = Math.max(...chartData)
|
|
77
|
+
const svgW = 700
|
|
78
|
+
const svgH = 220
|
|
79
|
+
const padL = 40
|
|
80
|
+
const padR = 10
|
|
81
|
+
const padT = 10
|
|
82
|
+
const padB = 30
|
|
83
|
+
const chartW = svgW - padL - padR
|
|
84
|
+
const chartH = svgH - padT - padB
|
|
85
|
+
const stepX = chartW / (chartData.length - 1)
|
|
86
|
+
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
|
87
|
+
const yLabels = ['$0', '$2k', '$4k', '$6k']
|
|
88
|
+
|
|
89
|
+
const points = chartData.map((val, i) => ({
|
|
90
|
+
x: padL + i * stepX,
|
|
91
|
+
y: padT + chartH - (val / maxVal) * chartH,
|
|
92
|
+
}))
|
|
93
|
+
|
|
94
|
+
const linePath = points.map((p, i) => {
|
|
95
|
+
if (i === 0) return `M ${p.x} ${p.y}`
|
|
96
|
+
const prev = points[i - 1]
|
|
97
|
+
const cpx1 = prev.x + stepX * 0.4
|
|
98
|
+
const cpx2 = p.x - stepX * 0.4
|
|
99
|
+
return `C ${cpx1} ${prev.y}, ${cpx2} ${p.y}, ${p.x} ${p.y}`
|
|
100
|
+
}).join(' ')
|
|
101
|
+
|
|
102
|
+
const areaPath = `${linePath} L ${points[points.length - 1].x} ${padT + chartH} L ${points[0].x} ${padT + chartH} Z`
|
|
103
|
+
|
|
104
|
+
function yPos(index: number) {
|
|
105
|
+
return padT + chartH - (index / (yLabels.length - 1)) * chartH
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function xLabelPos(index: number) {
|
|
109
|
+
const idx = Math.round((index / (days.length - 1)) * (chartData.length - 1))
|
|
110
|
+
return points[idx].x
|
|
111
|
+
}
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
<template>
|
|
115
|
+
<AuthenticatedLayout
|
|
116
|
+
:current-user="currentUser"
|
|
117
|
+
:app-name="appName"
|
|
118
|
+
:navigate="navigate"
|
|
119
|
+
active-path="/dashboard"
|
|
120
|
+
>
|
|
121
|
+
<Header :navigate="navigate">
|
|
122
|
+
<TopNav :links="topNav" :on-link-click="navigate" />
|
|
123
|
+
</Header>
|
|
124
|
+
<Main>
|
|
125
|
+
<div class="space-y-6">
|
|
126
|
+
<!-- Page title row -->
|
|
127
|
+
<div class="flex items-center justify-between">
|
|
128
|
+
<h2 class="text-3xl font-bold tracking-tight">Dashboard</h2>
|
|
129
|
+
<div class="flex items-center gap-1 rounded-lg border bg-card p-1">
|
|
130
|
+
<button
|
|
131
|
+
v-for="range in ranges"
|
|
132
|
+
:key="range"
|
|
133
|
+
:class="[
|
|
134
|
+
'rounded-md px-3 py-1.5 text-xs font-medium transition-colors',
|
|
135
|
+
activeRange === range
|
|
136
|
+
? 'bg-primary text-primary-foreground shadow-sm'
|
|
137
|
+
: 'text-muted-foreground hover:text-foreground',
|
|
138
|
+
]"
|
|
139
|
+
@click="activeRange = range"
|
|
140
|
+
>
|
|
141
|
+
{{ rangeLabel(range) }}
|
|
142
|
+
</button>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<!-- Stat cards -->
|
|
147
|
+
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
148
|
+
<Card>
|
|
149
|
+
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
150
|
+
<CardTitle class="text-sm font-medium">Total Revenue</CardTitle>
|
|
151
|
+
<DollarSign class="h-4 w-4 text-muted-foreground" />
|
|
152
|
+
</CardHeader>
|
|
153
|
+
<CardContent>
|
|
154
|
+
<div class="text-2xl font-bold">$45,231.89</div>
|
|
155
|
+
<div class="mt-1 flex items-center gap-1">
|
|
156
|
+
<Badge variant="outline" class="border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 gap-0.5 text-xs font-medium">
|
|
157
|
+
<ArrowUpRight class="h-3 w-3" />
|
|
158
|
+
+20.1%
|
|
159
|
+
</Badge>
|
|
160
|
+
<span class="text-xs text-muted-foreground">from last period</span>
|
|
161
|
+
</div>
|
|
162
|
+
</CardContent>
|
|
163
|
+
</Card>
|
|
164
|
+
|
|
165
|
+
<Card>
|
|
166
|
+
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
167
|
+
<CardTitle class="text-sm font-medium">New Customers</CardTitle>
|
|
168
|
+
<Users class="h-4 w-4 text-muted-foreground" />
|
|
169
|
+
</CardHeader>
|
|
170
|
+
<CardContent>
|
|
171
|
+
<div class="text-2xl font-bold">+2,350</div>
|
|
172
|
+
<div class="mt-1 flex items-center gap-1">
|
|
173
|
+
<Badge variant="outline" class="border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 gap-0.5 text-xs font-medium">
|
|
174
|
+
<ArrowUpRight class="h-3 w-3" />
|
|
175
|
+
+180.1%
|
|
176
|
+
</Badge>
|
|
177
|
+
<span class="text-xs text-muted-foreground">from last period</span>
|
|
178
|
+
</div>
|
|
179
|
+
</CardContent>
|
|
180
|
+
</Card>
|
|
181
|
+
|
|
182
|
+
<Card>
|
|
183
|
+
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
184
|
+
<CardTitle class="text-sm font-medium">Active Subscriptions</CardTitle>
|
|
185
|
+
<CreditCard class="h-4 w-4 text-muted-foreground" />
|
|
186
|
+
</CardHeader>
|
|
187
|
+
<CardContent>
|
|
188
|
+
<div class="text-2xl font-bold">+12,234</div>
|
|
189
|
+
<div class="mt-1 flex items-center gap-1">
|
|
190
|
+
<Badge variant="outline" class="border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 gap-0.5 text-xs font-medium">
|
|
191
|
+
<ArrowUpRight class="h-3 w-3" />
|
|
192
|
+
+19%
|
|
193
|
+
</Badge>
|
|
194
|
+
<span class="text-xs text-muted-foreground">from last period</span>
|
|
195
|
+
</div>
|
|
196
|
+
</CardContent>
|
|
197
|
+
</Card>
|
|
198
|
+
|
|
199
|
+
<Card>
|
|
200
|
+
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
201
|
+
<CardTitle class="text-sm font-medium">Churn Rate</CardTitle>
|
|
202
|
+
<Activity class="h-4 w-4 text-muted-foreground" />
|
|
203
|
+
</CardHeader>
|
|
204
|
+
<CardContent>
|
|
205
|
+
<div class="text-2xl font-bold">2.4%</div>
|
|
206
|
+
<div class="mt-1 flex items-center gap-1">
|
|
207
|
+
<Badge variant="outline" class="border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 gap-0.5 text-xs font-medium">
|
|
208
|
+
<ArrowDownRight class="h-3 w-3" />
|
|
209
|
+
-0.3%
|
|
210
|
+
</Badge>
|
|
211
|
+
<span class="text-xs text-muted-foreground">from last period</span>
|
|
212
|
+
</div>
|
|
213
|
+
</CardContent>
|
|
214
|
+
</Card>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<!-- Revenue chart -->
|
|
218
|
+
<Card>
|
|
219
|
+
<CardHeader>
|
|
220
|
+
<div class="flex items-center justify-between">
|
|
221
|
+
<div>
|
|
222
|
+
<CardTitle>Revenue Overview</CardTitle>
|
|
223
|
+
<CardDescription>Daily revenue for the selected period</CardDescription>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="flex items-center gap-2 text-sm">
|
|
226
|
+
<TrendingUp class="h-4 w-4 text-emerald-600" />
|
|
227
|
+
<span class="font-medium text-emerald-600">+12.5%</span>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</CardHeader>
|
|
231
|
+
<CardContent>
|
|
232
|
+
<svg :viewBox="`0 0 ${svgW} ${svgH}`" class="h-[280px] w-full" preserveAspectRatio="xMidYMid meet">
|
|
233
|
+
<defs>
|
|
234
|
+
<linearGradient id="areaGradient" x1="0" y1="0" x2="0" y2="1">
|
|
235
|
+
<stop offset="0%" class="[stop-color:var(--color-primary)]" stop-opacity="0.3" />
|
|
236
|
+
<stop offset="100%" class="[stop-color:var(--color-primary)]" stop-opacity="0.02" />
|
|
237
|
+
</linearGradient>
|
|
238
|
+
</defs>
|
|
239
|
+
|
|
240
|
+
<!-- Grid lines -->
|
|
241
|
+
<line
|
|
242
|
+
v-for="(_, i) in yLabels"
|
|
243
|
+
:key="'grid-' + i"
|
|
244
|
+
:x1="padL"
|
|
245
|
+
:y1="yPos(i)"
|
|
246
|
+
:x2="svgW - padR"
|
|
247
|
+
:y2="yPos(i)"
|
|
248
|
+
class="stroke-border"
|
|
249
|
+
stroke-width="0.5"
|
|
250
|
+
stroke-dasharray="4 4"
|
|
251
|
+
/>
|
|
252
|
+
|
|
253
|
+
<!-- Y-axis labels -->
|
|
254
|
+
<text
|
|
255
|
+
v-for="(label, i) in yLabels"
|
|
256
|
+
:key="'ylabel-' + i"
|
|
257
|
+
:x="padL - 6"
|
|
258
|
+
:y="yPos(i) + 3"
|
|
259
|
+
text-anchor="end"
|
|
260
|
+
class="fill-muted-foreground text-[9px]"
|
|
261
|
+
>
|
|
262
|
+
{{ label }}
|
|
263
|
+
</text>
|
|
264
|
+
|
|
265
|
+
<!-- Area fill -->
|
|
266
|
+
<path :d="areaPath" fill="url(#areaGradient)" />
|
|
267
|
+
|
|
268
|
+
<!-- Line -->
|
|
269
|
+
<path :d="linePath" fill="none" class="stroke-primary" stroke-width="2" stroke-linecap="round" />
|
|
270
|
+
|
|
271
|
+
<!-- X-axis labels -->
|
|
272
|
+
<text
|
|
273
|
+
v-for="(day, i) in days"
|
|
274
|
+
:key="'xlabel-' + i"
|
|
275
|
+
:x="xLabelPos(i)"
|
|
276
|
+
:y="svgH - 6"
|
|
277
|
+
text-anchor="middle"
|
|
278
|
+
class="fill-muted-foreground text-[9px]"
|
|
279
|
+
>
|
|
280
|
+
{{ day }}
|
|
281
|
+
</text>
|
|
282
|
+
</svg>
|
|
283
|
+
</CardContent>
|
|
284
|
+
</Card>
|
|
285
|
+
|
|
286
|
+
<!-- Recent Transactions -->
|
|
287
|
+
<Card>
|
|
288
|
+
<CardHeader>
|
|
289
|
+
<CardTitle>Recent Transactions</CardTitle>
|
|
290
|
+
<CardDescription>Latest payment activity across all channels</CardDescription>
|
|
291
|
+
</CardHeader>
|
|
292
|
+
<CardContent>
|
|
293
|
+
<Table>
|
|
294
|
+
<TableHeader>
|
|
295
|
+
<TableRow>
|
|
296
|
+
<TableHead>Customer</TableHead>
|
|
297
|
+
<TableHead>Amount</TableHead>
|
|
298
|
+
<TableHead>Status</TableHead>
|
|
299
|
+
<TableHead class="text-right">Date</TableHead>
|
|
300
|
+
</TableRow>
|
|
301
|
+
</TableHeader>
|
|
302
|
+
<TableBody>
|
|
303
|
+
<TableRow v-for="tx in transactions" :key="tx.email">
|
|
304
|
+
<TableCell>
|
|
305
|
+
<div>
|
|
306
|
+
<p class="font-medium">{{ tx.customer }}</p>
|
|
307
|
+
<p class="text-sm text-muted-foreground">{{ tx.email }}</p>
|
|
308
|
+
</div>
|
|
309
|
+
</TableCell>
|
|
310
|
+
<TableCell class="font-medium">{{ tx.amount }}</TableCell>
|
|
311
|
+
<TableCell>
|
|
312
|
+
<Badge variant="outline" :class="statusColor(tx.status)">
|
|
313
|
+
{{ tx.status }}
|
|
314
|
+
</Badge>
|
|
315
|
+
</TableCell>
|
|
316
|
+
<TableCell class="text-right text-muted-foreground">{{ tx.date }}</TableCell>
|
|
317
|
+
</TableRow>
|
|
318
|
+
</TableBody>
|
|
319
|
+
</Table>
|
|
320
|
+
</CardContent>
|
|
321
|
+
</Card>
|
|
322
|
+
</div>
|
|
323
|
+
</Main>
|
|
324
|
+
</AuthenticatedLayout>
|
|
325
|
+
</template>
|