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,118 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { post } from '@/lib/api'
|
|
4
|
+
import { Button } from '@/components/ui/button'
|
|
5
|
+
import { Input } from '@/components/ui/input'
|
|
6
|
+
import { Label } from '@/components/ui/label'
|
|
7
|
+
import { Separator } from '@/components/ui/separator'
|
|
8
|
+
|
|
9
|
+
const props = withDefaults(defineProps<{
|
|
10
|
+
appName?: string
|
|
11
|
+
navigate?: (href: string) => void
|
|
12
|
+
}>(), {
|
|
13
|
+
appName: 'Mantiq',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const email = ref('')
|
|
17
|
+
const password = ref('')
|
|
18
|
+
const error = ref('')
|
|
19
|
+
const loading = ref(false)
|
|
20
|
+
|
|
21
|
+
async function handleSubmit() {
|
|
22
|
+
error.value = ''
|
|
23
|
+
loading.value = true
|
|
24
|
+
const { ok, data } = await post('/login', { email: email.value, password: password.value })
|
|
25
|
+
if (ok) props.navigate?.('/dashboard')
|
|
26
|
+
else error.value = data?.error ?? 'Invalid email or password. Please try again.'
|
|
27
|
+
loading.value = false
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<div class="min-h-screen bg-muted/50 flex items-center justify-center p-4">
|
|
33
|
+
<div class="w-full max-w-sm">
|
|
34
|
+
<!-- Logo -->
|
|
35
|
+
<div class="mb-8 flex flex-col items-center gap-3">
|
|
36
|
+
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-primary text-primary-foreground text-sm font-bold shadow-sm">
|
|
37
|
+
M
|
|
38
|
+
</div>
|
|
39
|
+
<span class="text-lg font-semibold tracking-tight">{{ appName }}</span>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Card -->
|
|
43
|
+
<div class="rounded-lg border bg-card shadow-sm p-6">
|
|
44
|
+
<div class="mb-6">
|
|
45
|
+
<h1 class="text-xl font-semibold tracking-tight">Sign in</h1>
|
|
46
|
+
<p class="mt-1 text-sm text-muted-foreground">
|
|
47
|
+
Enter your credentials to continue
|
|
48
|
+
</p>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div v-if="error" class="mb-4 rounded-md border border-destructive px-4 py-3 text-sm text-destructive">
|
|
52
|
+
{{ error }}
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<form class="space-y-4" @submit.prevent="handleSubmit">
|
|
56
|
+
<div class="space-y-2">
|
|
57
|
+
<Label for="email">Email</Label>
|
|
58
|
+
<Input
|
|
59
|
+
id="email"
|
|
60
|
+
v-model="email"
|
|
61
|
+
type="email"
|
|
62
|
+
required
|
|
63
|
+
placeholder="you@example.com"
|
|
64
|
+
autocomplete="email"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="space-y-2">
|
|
68
|
+
<div class="flex items-center justify-between">
|
|
69
|
+
<Label for="password">Password</Label>
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
class="text-xs font-medium text-primary hover:text-primary/80"
|
|
73
|
+
tabindex="-1"
|
|
74
|
+
>
|
|
75
|
+
Forgot password?
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
<Input
|
|
79
|
+
id="password"
|
|
80
|
+
v-model="password"
|
|
81
|
+
type="password"
|
|
82
|
+
required
|
|
83
|
+
placeholder="Enter your password"
|
|
84
|
+
autocomplete="current-password"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
<Button type="submit" class="w-full" :disabled="loading">
|
|
88
|
+
{{ loading ? 'Signing in...' : 'Sign in' }}
|
|
89
|
+
</Button>
|
|
90
|
+
</form>
|
|
91
|
+
|
|
92
|
+
<div class="relative my-6">
|
|
93
|
+
<div class="absolute inset-0 flex items-center">
|
|
94
|
+
<Separator class="w-full" />
|
|
95
|
+
</div>
|
|
96
|
+
<div class="relative flex justify-center text-xs uppercase">
|
|
97
|
+
<span class="bg-card px-2 text-muted-foreground">or</span>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<p class="text-center text-sm text-muted-foreground">
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
class="font-medium text-primary hover:text-primary/80"
|
|
105
|
+
@click="navigate?.('/register')"
|
|
106
|
+
>
|
|
107
|
+
Create an account
|
|
108
|
+
</button>
|
|
109
|
+
</p>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<!-- Footer -->
|
|
113
|
+
<p class="mt-6 text-center text-xs text-muted-foreground">
|
|
114
|
+
Powered by {{ appName }}
|
|
115
|
+
</p>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</template>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { post } from '@/lib/api'
|
|
4
|
+
import { Button } from '@/components/ui/button'
|
|
5
|
+
import { Input } from '@/components/ui/input'
|
|
6
|
+
import { Label } from '@/components/ui/label'
|
|
7
|
+
import { Separator } from '@/components/ui/separator'
|
|
8
|
+
|
|
9
|
+
const props = withDefaults(defineProps<{
|
|
10
|
+
appName?: string
|
|
11
|
+
navigate?: (href: string) => void
|
|
12
|
+
}>(), {
|
|
13
|
+
appName: 'Mantiq',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const name = ref('')
|
|
17
|
+
const email = ref('')
|
|
18
|
+
const password = ref('')
|
|
19
|
+
const error = ref('')
|
|
20
|
+
const loading = ref(false)
|
|
21
|
+
|
|
22
|
+
async function handleSubmit() {
|
|
23
|
+
error.value = ''
|
|
24
|
+
loading.value = true
|
|
25
|
+
const { ok, data } = await post('/register', { name: name.value, email: email.value, password: password.value })
|
|
26
|
+
if (ok) props.navigate?.('/dashboard')
|
|
27
|
+
else error.value = data?.error?.message ?? data?.error ?? 'Registration failed. Please try again.'
|
|
28
|
+
loading.value = false
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div class="min-h-screen bg-muted/50 flex items-center justify-center p-4">
|
|
34
|
+
<div class="w-full max-w-sm">
|
|
35
|
+
<!-- Logo -->
|
|
36
|
+
<div class="mb-8 flex flex-col items-center gap-3">
|
|
37
|
+
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-primary text-primary-foreground text-sm font-bold shadow-sm">
|
|
38
|
+
M
|
|
39
|
+
</div>
|
|
40
|
+
<span class="text-lg font-semibold tracking-tight">{{ appName }}</span>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<!-- Card -->
|
|
44
|
+
<div class="rounded-lg border bg-card shadow-sm p-6">
|
|
45
|
+
<div class="mb-6">
|
|
46
|
+
<h1 class="text-xl font-semibold tracking-tight">Create an account</h1>
|
|
47
|
+
<p class="mt-1 text-sm text-muted-foreground">
|
|
48
|
+
Get started with {{ appName }} today
|
|
49
|
+
</p>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div v-if="error" class="mb-4 rounded-md border border-destructive px-4 py-3 text-sm text-destructive">
|
|
53
|
+
{{ error }}
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<form class="space-y-4" @submit.prevent="handleSubmit">
|
|
57
|
+
<div class="space-y-2">
|
|
58
|
+
<Label for="name">Name</Label>
|
|
59
|
+
<Input
|
|
60
|
+
id="name"
|
|
61
|
+
v-model="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
|
+
v-model="email"
|
|
72
|
+
type="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
|
+
v-model="password"
|
|
83
|
+
type="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
|
+
@click="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>
|
|
139
|
+
</template>
|
|
@@ -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,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Sidebar,
|
|
3
|
+
SidebarContent,
|
|
4
|
+
SidebarFooter,
|
|
5
|
+
SidebarHeader,
|
|
6
|
+
SidebarMenu,
|
|
7
|
+
SidebarMenuButton,
|
|
8
|
+
SidebarMenuItem,
|
|
9
|
+
} from '@/components/ui/sidebar'
|
|
10
|
+
import { NavGroup } from './nav-group'
|
|
11
|
+
import { NavUser, type NavUserProps } from './nav-user'
|
|
12
|
+
import { sidebarData } from './sidebar-data'
|
|
13
|
+
import { Command } from 'lucide-react'
|
|
14
|
+
|
|
15
|
+
export interface AppSidebarProps {
|
|
16
|
+
user: NavUserProps['user']
|
|
17
|
+
appName: string
|
|
18
|
+
activePath: string
|
|
19
|
+
navigate: (href: string) => void
|
|
20
|
+
onLogout: () => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function AppSidebar({
|
|
24
|
+
user,
|
|
25
|
+
appName,
|
|
26
|
+
activePath,
|
|
27
|
+
navigate,
|
|
28
|
+
onLogout,
|
|
29
|
+
}: AppSidebarProps) {
|
|
30
|
+
return (
|
|
31
|
+
<Sidebar variant="sidebar" collapsible="icon">
|
|
32
|
+
<SidebarHeader>
|
|
33
|
+
<SidebarMenu>
|
|
34
|
+
<SidebarMenuItem>
|
|
35
|
+
<SidebarMenuButton
|
|
36
|
+
size="lg"
|
|
37
|
+
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
38
|
+
onClick={() => navigate('/dashboard')}
|
|
39
|
+
tooltip={appName}
|
|
40
|
+
>
|
|
41
|
+
<div className="flex aspect-square size-8 items-center justify-center rounded-none bg-sidebar-primary text-sidebar-primary-foreground">
|
|
42
|
+
<Command className="size-4" />
|
|
43
|
+
</div>
|
|
44
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
45
|
+
<span className="truncate font-semibold">{appName}</span>
|
|
46
|
+
</div>
|
|
47
|
+
</SidebarMenuButton>
|
|
48
|
+
</SidebarMenuItem>
|
|
49
|
+
</SidebarMenu>
|
|
50
|
+
</SidebarHeader>
|
|
51
|
+
|
|
52
|
+
<SidebarContent>
|
|
53
|
+
{sidebarData.map((group) => (
|
|
54
|
+
<NavGroup
|
|
55
|
+
key={group.title}
|
|
56
|
+
group={group}
|
|
57
|
+
activePath={activePath}
|
|
58
|
+
navigate={navigate}
|
|
59
|
+
/>
|
|
60
|
+
))}
|
|
61
|
+
</SidebarContent>
|
|
62
|
+
|
|
63
|
+
<SidebarFooter>
|
|
64
|
+
<NavUser user={user} navigate={navigate} onLogout={onLogout} />
|
|
65
|
+
</SidebarFooter>
|
|
66
|
+
</Sidebar>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
import { post } from '@/lib/api'
|
|
3
|
+
import { SidebarProvider, SidebarInset } from '@/components/ui/sidebar'
|
|
4
|
+
import { AppSidebar } from './app-sidebar'
|
|
5
|
+
|
|
6
|
+
interface AuthenticatedLayoutProps {
|
|
7
|
+
children: React.ReactNode
|
|
8
|
+
currentUser?: { name: string; email: string; role?: string } | null
|
|
9
|
+
appName?: string
|
|
10
|
+
navigate: (href: string) => void
|
|
11
|
+
activePath: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function AuthenticatedLayout({
|
|
15
|
+
children,
|
|
16
|
+
currentUser,
|
|
17
|
+
appName = 'Mantiq',
|
|
18
|
+
navigate,
|
|
19
|
+
activePath,
|
|
20
|
+
}: AuthenticatedLayoutProps) {
|
|
21
|
+
const handleLogout = useCallback(async () => {
|
|
22
|
+
await post('/logout', {})
|
|
23
|
+
navigate('/login')
|
|
24
|
+
}, [navigate])
|
|
25
|
+
|
|
26
|
+
const user = currentUser ?? { name: 'User', email: 'user@example.com' }
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<SidebarProvider defaultOpen={false}>
|
|
30
|
+
<AppSidebar
|
|
31
|
+
user={user}
|
|
32
|
+
appName={appName}
|
|
33
|
+
activePath={activePath}
|
|
34
|
+
navigate={navigate}
|
|
35
|
+
onLogout={handleLogout}
|
|
36
|
+
/>
|
|
37
|
+
<SidebarInset>
|
|
38
|
+
{children}
|
|
39
|
+
</SidebarInset>
|
|
40
|
+
</SidebarProvider>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { AuthenticatedLayout } from '@/components/layout/authenticated-layout'
|
|
2
|
+
import { Header } from '@/components/layout/header'
|
|
3
|
+
import { Main } from '@/components/layout/main'
|
|
4
|
+
import {
|
|
5
|
+
Circle,
|
|
6
|
+
CheckCircle2,
|
|
7
|
+
ArrowUpRight,
|
|
8
|
+
Clock,
|
|
9
|
+
AlertCircle,
|
|
10
|
+
Minus,
|
|
11
|
+
} from 'lucide-react'
|
|
12
|
+
|
|
13
|
+
interface DashboardProps {
|
|
14
|
+
appName?: string
|
|
15
|
+
currentUser?: { id: number; name: string; email: string; role: string } | null
|
|
16
|
+
navigate: (href: string) => void
|
|
17
|
+
[key: string]: any
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Priority = 'urgent' | 'high' | 'medium' | 'low' | 'none'
|
|
21
|
+
type Status = 'done' | 'in-progress' | 'todo' | 'backlog'
|
|
22
|
+
|
|
23
|
+
const issues: {
|
|
24
|
+
id: string
|
|
25
|
+
title: string
|
|
26
|
+
status: Status
|
|
27
|
+
priority: Priority
|
|
28
|
+
assignee: string
|
|
29
|
+
updated: string
|
|
30
|
+
}[] = [
|
|
31
|
+
{ id: 'MNT-142', title: 'Fix authentication token refresh race condition', status: 'in-progress', priority: 'urgent', assignee: 'AK', updated: '2m' },
|
|
32
|
+
{ id: 'MNT-141', title: 'Add rate limiting to public API endpoints', status: 'todo', priority: 'high', assignee: 'SC', updated: '15m' },
|
|
33
|
+
{ id: 'MNT-140', title: 'Migrate user sessions to Redis store', status: 'in-progress', priority: 'high', assignee: 'AK', updated: '1h' },
|
|
34
|
+
{ id: 'MNT-139', title: 'Update Stripe webhook handler for v2024-12', status: 'todo', priority: 'medium', assignee: 'JL', updated: '2h' },
|
|
35
|
+
{ id: 'MNT-138', title: 'Implement CSV export for analytics dashboard', status: 'done', priority: 'medium', assignee: 'NR', updated: '3h' },
|
|
36
|
+
{ id: 'MNT-137', title: 'Add OpenTelemetry tracing to queue workers', status: 'backlog', priority: 'low', assignee: 'SC', updated: '5h' },
|
|
37
|
+
{ id: 'MNT-136', title: 'Refactor notification preferences into per-channel config', status: 'done', priority: 'medium', assignee: 'AK', updated: '6h' },
|
|
38
|
+
{ id: 'MNT-135', title: 'Set up staging environment with seed data', status: 'backlog', priority: 'low', assignee: 'JL', updated: '1d' },
|
|
39
|
+
{ id: 'MNT-134', title: 'Write integration tests for payment flow', status: 'todo', priority: 'high', assignee: 'NR', updated: '1d' },
|
|
40
|
+
{ id: 'MNT-133', title: 'Audit and update npm dependencies', status: 'backlog', priority: 'none', assignee: 'SC', updated: '2d' },
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
const StatusIcon = ({ status }: { status: Status }) => {
|
|
44
|
+
switch (status) {
|
|
45
|
+
case 'done':
|
|
46
|
+
return <CheckCircle2 className="h-3.5 w-3.5 text-primary" />
|
|
47
|
+
case 'in-progress':
|
|
48
|
+
return <Clock className="h-3.5 w-3.5 text-amber-500" />
|
|
49
|
+
case 'todo':
|
|
50
|
+
return <Circle className="h-3.5 w-3.5 text-muted-foreground" />
|
|
51
|
+
case 'backlog':
|
|
52
|
+
return <Minus className="h-3.5 w-3.5 text-muted-foreground/50" />
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const PriorityIndicator = ({ priority }: { priority: Priority }) => {
|
|
57
|
+
const colors: Record<Priority, string> = {
|
|
58
|
+
urgent: 'bg-red-500',
|
|
59
|
+
high: 'bg-orange-500',
|
|
60
|
+
medium: 'bg-yellow-500',
|
|
61
|
+
low: 'bg-blue-400',
|
|
62
|
+
none: 'bg-muted-foreground/30',
|
|
63
|
+
}
|
|
64
|
+
return (
|
|
65
|
+
<div className="flex items-center gap-0.5" title={priority}>
|
|
66
|
+
{[0, 1, 2, 3].map((i) => (
|
|
67
|
+
<div
|
|
68
|
+
key={i}
|
|
69
|
+
className={`h-2.5 w-[3px] rounded-[1px] ${
|
|
70
|
+
(priority === 'urgent' && i <= 3) ||
|
|
71
|
+
(priority === 'high' && i <= 2) ||
|
|
72
|
+
(priority === 'medium' && i <= 1) ||
|
|
73
|
+
(priority === 'low' && i <= 0)
|
|
74
|
+
? colors[priority]
|
|
75
|
+
: 'bg-muted-foreground/15'
|
|
76
|
+
}`}
|
|
77
|
+
/>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default function Dashboard({
|
|
84
|
+
appName = 'Mantiq',
|
|
85
|
+
currentUser,
|
|
86
|
+
navigate,
|
|
87
|
+
}: DashboardProps) {
|
|
88
|
+
const doneCount = issues.filter((i) => i.status === 'done').length
|
|
89
|
+
const inProgressCount = issues.filter((i) => i.status === 'in-progress').length
|
|
90
|
+
const todoCount = issues.filter((i) => i.status === 'todo').length
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<AuthenticatedLayout
|
|
94
|
+
currentUser={currentUser}
|
|
95
|
+
appName={appName}
|
|
96
|
+
navigate={navigate}
|
|
97
|
+
activePath="/dashboard"
|
|
98
|
+
>
|
|
99
|
+
<Header navigate={navigate} />
|
|
100
|
+
<Main>
|
|
101
|
+
<div className="space-y-1">
|
|
102
|
+
{/* Compact header row */}
|
|
103
|
+
<div className="flex items-center justify-between py-3">
|
|
104
|
+
<div className="flex items-center gap-6">
|
|
105
|
+
<h2 className="text-sm font-semibold">My Issues</h2>
|
|
106
|
+
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
|
107
|
+
<span className="flex items-center gap-1.5">
|
|
108
|
+
<Clock className="h-3 w-3 text-amber-500" />
|
|
109
|
+
{inProgressCount} in progress
|
|
110
|
+
</span>
|
|
111
|
+
<span className="flex items-center gap-1.5">
|
|
112
|
+
<Circle className="h-3 w-3" />
|
|
113
|
+
{todoCount} todo
|
|
114
|
+
</span>
|
|
115
|
+
<span className="flex items-center gap-1.5">
|
|
116
|
+
<CheckCircle2 className="h-3 w-3 text-primary" />
|
|
117
|
+
{doneCount} done
|
|
118
|
+
</span>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
<div className="flex items-center gap-2">
|
|
122
|
+
<kbd className="hidden sm:inline-flex items-center gap-0.5 rounded border bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
|
|
123
|
+
<span className="text-xs">⌘</span>K
|
|
124
|
+
</kbd>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{/* Issue list — no cards, just rows */}
|
|
129
|
+
<div className="border-t">
|
|
130
|
+
{issues.map((issue) => (
|
|
131
|
+
<div
|
|
132
|
+
key={issue.id}
|
|
133
|
+
className="group flex items-center gap-3 border-b px-1 py-2 transition-colors hover:bg-muted/50 cursor-default"
|
|
134
|
+
>
|
|
135
|
+
<PriorityIndicator priority={issue.priority} />
|
|
136
|
+
<StatusIcon status={issue.status} />
|
|
137
|
+
<span className="font-mono-num text-xs text-muted-foreground w-16 shrink-0">
|
|
138
|
+
{issue.id}
|
|
139
|
+
</span>
|
|
140
|
+
<span className={`flex-1 text-sm truncate ${issue.status === 'done' ? 'line-through text-muted-foreground' : ''}`}>
|
|
141
|
+
{issue.title}
|
|
142
|
+
</span>
|
|
143
|
+
<div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-muted text-[10px] font-medium text-muted-foreground">
|
|
144
|
+
{issue.assignee}
|
|
145
|
+
</div>
|
|
146
|
+
<span className="font-mono-num text-xs text-muted-foreground w-8 text-right shrink-0">
|
|
147
|
+
{issue.updated}
|
|
148
|
+
</span>
|
|
149
|
+
<ArrowUpRight className="h-3.5 w-3.5 text-muted-foreground/0 group-hover:text-muted-foreground transition-colors shrink-0" />
|
|
150
|
+
</div>
|
|
151
|
+
))}
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{/* Bottom counts */}
|
|
155
|
+
<div className="flex items-center justify-between pt-2 text-xs text-muted-foreground">
|
|
156
|
+
<span>{issues.length} issues</span>
|
|
157
|
+
<span className="flex items-center gap-1">
|
|
158
|
+
<AlertCircle className="h-3 w-3" />
|
|
159
|
+
{issues.filter((i) => i.priority === 'urgent').length} urgent
|
|
160
|
+
</span>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</Main>
|
|
164
|
+
</AuthenticatedLayout>
|
|
165
|
+
)
|
|
166
|
+
}
|