vasp-cli 0.3.1 → 0.9.0
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/dist/vasp +80 -31
- package/package.json +2 -2
- package/starters/minimal.vasp +1 -1
- package/starters/recipe.vasp +70 -0
- package/starters/todo-auth-ssr.vasp +33 -20
- package/starters/todo.vasp +15 -8
- package/templates/shared/.gitignore.hbs +1 -0
- package/templates/shared/README.md.hbs +53 -0
- package/templates/shared/auth/client/Login.vue.hbs +1 -1
- package/templates/shared/auth/client/Register.vue.hbs +1 -1
- package/templates/shared/auth/server/index.hbs +4 -8
- package/templates/shared/auth/server/middleware.hbs +33 -15
- package/templates/shared/auth/server/plugin.hbs +7 -0
- package/templates/shared/auth/server/providers/github.hbs +1 -1
- package/templates/shared/auth/server/providers/google.hbs +1 -1
- package/templates/shared/auth/server/providers/usernameAndPassword.hbs +3 -6
- package/templates/shared/bunfig.toml.hbs +3 -0
- package/templates/shared/drizzle/schema.hbs +39 -9
- package/templates/shared/jobs/_job.hbs +12 -2
- package/templates/shared/package.json.hbs +14 -4
- package/templates/shared/server/db/client.hbs +19 -1
- package/templates/shared/server/db/seed.hbs +16 -0
- package/templates/shared/server/index.hbs +48 -0
- package/templates/shared/server/middleware/errorHandler.hbs +75 -0
- package/templates/shared/server/middleware/logger.hbs +74 -0
- package/templates/{templates/shared → shared}/server/middleware/rateLimit.hbs +2 -2
- package/templates/shared/server/routes/_vasp.hbs +37 -0
- package/templates/shared/server/routes/actions/_action.hbs +5 -1
- package/templates/shared/server/routes/api/_api.hbs +24 -0
- package/templates/shared/server/routes/crud/_crud.hbs +103 -11
- package/templates/shared/server/routes/queries/_query.hbs +5 -1
- package/templates/shared/server/routes/realtime/_channel.hbs +58 -10
- package/templates/shared/shared/types.hbs +58 -0
- package/templates/shared/shared/validation.hbs +20 -0
- package/templates/shared/tests/actions/_action.test.js.hbs +7 -0
- package/templates/shared/tests/actions/_action.test.ts.hbs +7 -0
- package/templates/shared/tests/auth/login.test.js.hbs +7 -0
- package/templates/shared/tests/auth/login.test.ts.hbs +7 -0
- package/templates/shared/tests/crud/_entity.test.js.hbs +7 -0
- package/templates/shared/tests/crud/_entity.test.ts.hbs +7 -0
- package/templates/shared/tests/queries/_query.test.js.hbs +7 -0
- package/templates/shared/tests/queries/_query.test.ts.hbs +7 -0
- package/templates/shared/tests/setup.js.hbs +5 -0
- package/templates/shared/tests/setup.ts.hbs +5 -0
- package/templates/shared/tests/vitest.config.js.hbs +8 -0
- package/templates/shared/tests/vitest.config.ts.hbs +8 -0
- package/templates/shared/tsconfig.json.hbs +2 -1
- package/templates/spa/js/src/App.vue.hbs +9 -1
- package/templates/spa/js/src/components/VaspErrorBoundary.vue.hbs +33 -0
- package/templates/spa/js/src/components/VaspNotifications.vue.hbs +60 -0
- package/templates/spa/js/src/vasp/auth.js.hbs +31 -15
- package/templates/spa/js/src/vasp/client/actions.js.hbs +7 -1
- package/templates/spa/js/src/vasp/client/crud.js.hbs +94 -5
- package/templates/spa/js/src/vasp/useVaspNotifications.js.hbs +35 -0
- package/templates/spa/js/vite.config.js.hbs +1 -0
- package/templates/spa/ts/src/App.vue.hbs +9 -1
- package/templates/spa/ts/src/components/VaspErrorBoundary.vue.hbs +33 -0
- package/templates/spa/ts/src/components/VaspNotifications.vue.hbs +60 -0
- package/templates/spa/ts/src/vasp/auth.ts.hbs +31 -15
- package/templates/spa/ts/src/vasp/client/actions.ts.hbs +7 -1
- package/templates/spa/ts/src/vasp/client/crud.ts.hbs +96 -10
- package/templates/spa/ts/src/vasp/client/types.ts.hbs +14 -28
- package/templates/spa/ts/src/vasp/useVaspNotifications.ts.hbs +41 -0
- package/templates/spa/ts/vite.config.ts.hbs +1 -0
- package/templates/ssr/js/error.vue.hbs +23 -0
- package/templates/ssr/js/nuxt.config.js.hbs +1 -0
- package/templates/ssr/js/plugins/vasp.client.js.hbs +11 -1
- package/templates/ssr/ts/error.vue.hbs +26 -0
- package/templates/ssr/ts/nuxt.config.ts.hbs +1 -0
- package/templates/ssr/ts/plugins/vasp.client.ts.hbs +11 -1
- package/templates/starters/minimal.vasp +15 -0
- package/templates/starters/recipe.vasp +70 -0
- package/templates/starters/todo-auth-ssr.vasp +65 -0
- package/templates/starters/todo.vasp +42 -0
- package/templates/templates/shared/.gitignore.hbs +0 -8
- package/templates/templates/shared/auth/client/Login.vue.hbs +0 -46
- package/templates/templates/shared/auth/client/Register.vue.hbs +0 -42
- package/templates/templates/shared/auth/server/index.hbs +0 -51
- package/templates/templates/shared/auth/server/middleware.hbs +0 -33
- package/templates/templates/shared/auth/server/providers/github.hbs +0 -48
- package/templates/templates/shared/auth/server/providers/google.hbs +0 -53
- package/templates/templates/shared/auth/server/providers/usernameAndPassword.hbs +0 -69
- package/templates/templates/shared/bunfig.toml.hbs +0 -2
- package/templates/templates/shared/drizzle/schema.hbs +0 -48
- package/templates/templates/shared/jobs/_job.hbs +0 -34
- package/templates/templates/shared/jobs/boss.hbs +0 -15
- package/templates/templates/shared/package.json.hbs +0 -35
- package/templates/templates/shared/server/db/client.hbs +0 -12
- package/templates/templates/shared/server/index.hbs +0 -60
- package/templates/templates/shared/server/routes/actions/_action.hbs +0 -20
- package/templates/templates/shared/server/routes/crud/_crud.hbs +0 -86
- package/templates/templates/shared/server/routes/jobs/_schedule.hbs +0 -12
- package/templates/templates/shared/server/routes/queries/_query.hbs +0 -20
- package/templates/templates/shared/server/routes/realtime/_channel.hbs +0 -78
- package/templates/templates/shared/server/routes/realtime/index.hbs +0 -9
- package/templates/templates/shared/tsconfig.json.hbs +0 -21
- package/templates/templates/spa/js/index.html.hbs +0 -12
- package/templates/templates/spa/js/src/App.vue.hbs +0 -3
- package/templates/templates/spa/js/src/main.js.hbs +0 -9
- package/templates/templates/spa/js/src/router/index.js.hbs +0 -41
- package/templates/templates/spa/js/src/vasp/auth.js.hbs +0 -45
- package/templates/templates/spa/js/src/vasp/client/actions.js.hbs +0 -15
- package/templates/templates/spa/js/src/vasp/client/crud.js.hbs +0 -30
- package/templates/templates/spa/js/src/vasp/client/index.js.hbs +0 -16
- package/templates/templates/spa/js/src/vasp/client/queries.js.hbs +0 -15
- package/templates/templates/spa/js/src/vasp/client/realtime.js.hbs +0 -51
- package/templates/templates/spa/js/src/vasp/plugin.js.hbs +0 -11
- package/templates/templates/spa/js/vite.config.js.hbs +0 -26
- package/templates/templates/spa/ts/index.html.hbs +0 -12
- package/templates/templates/spa/ts/src/App.vue.hbs +0 -3
- package/templates/templates/spa/ts/src/main.ts.hbs +0 -9
- package/templates/templates/spa/ts/src/router/index.ts.hbs +0 -41
- package/templates/templates/spa/ts/src/vasp/auth.ts.hbs +0 -53
- package/templates/templates/spa/ts/src/vasp/client/actions.ts.hbs +0 -19
- package/templates/templates/spa/ts/src/vasp/client/crud.ts.hbs +0 -37
- package/templates/templates/spa/ts/src/vasp/client/index.ts.hbs +0 -17
- package/templates/templates/spa/ts/src/vasp/client/queries.ts.hbs +0 -19
- package/templates/templates/spa/ts/src/vasp/client/realtime.ts.hbs +0 -56
- package/templates/templates/spa/ts/src/vasp/client/types.ts.hbs +0 -33
- package/templates/templates/spa/ts/src/vasp/plugin.ts.hbs +0 -12
- package/templates/templates/spa/ts/vite.config.ts.hbs +0 -26
- package/templates/templates/ssr/js/_page.vue.hbs +0 -10
- package/templates/templates/ssr/js/app.vue.hbs +0 -3
- package/templates/templates/ssr/js/composables/useAuth.js.hbs +0 -52
- package/templates/templates/ssr/js/composables/useVasp.js.hbs +0 -6
- package/templates/templates/ssr/js/middleware/auth.js.hbs +0 -8
- package/templates/templates/ssr/js/nuxt.config.js.hbs +0 -15
- package/templates/templates/ssr/js/plugins/vasp.client.js.hbs +0 -27
- package/templates/templates/ssr/js/plugins/vasp.server.js.hbs +0 -33
- package/templates/templates/ssr/ts/_page.vue.hbs +0 -10
- package/templates/templates/ssr/ts/app.vue.hbs +0 -3
- package/templates/templates/ssr/ts/composables/useAuth.ts.hbs +0 -56
- package/templates/templates/ssr/ts/composables/useVasp.ts.hbs +0 -10
- package/templates/templates/ssr/ts/middleware/auth.ts.hbs +0 -8
- package/templates/templates/ssr/ts/nuxt.config.ts.hbs +0 -19
- package/templates/templates/ssr/ts/plugins/vasp.client.ts.hbs +0 -27
- package/templates/templates/ssr/ts/plugins/vasp.server.ts.hbs +0 -33
- /package/templates/{templates/shared/.env.example.hbs → shared/.env.hbs} +0 -0
- /package/templates/{templates/shared → shared}/drizzle/drizzle.config.hbs +0 -0
- /package/templates/{templates/shared → shared}/server/middleware/csrf.hbs +0 -0
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
"jsx": "preserve",
|
|
14
14
|
"paths": {
|
|
15
15
|
"@src/*": ["./src/*"],
|
|
16
|
+
"@shared/*": ["./shared/*"],
|
|
16
17
|
"@vasp-framework/client": ["./src/vasp/client/index.ts"]
|
|
17
18
|
}
|
|
18
19
|
},
|
|
19
|
-
"include": ["src/**/*.ts", "src/**/*.vue", "server/**/*.ts", "drizzle/**/*.ts"],
|
|
20
|
+
"include": ["src/**/*.ts", "src/**/*.vue", "server/**/*.ts", "shared/**/*.ts", "drizzle/**/*.ts"],
|
|
20
21
|
"exclude": ["node_modules", "dist", ".vasp-gen"]
|
|
21
22
|
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import VaspErrorBoundary from './components/VaspErrorBoundary.vue'
|
|
3
|
+
import VaspNotifications from './components/VaspNotifications.vue'
|
|
4
|
+
</script>
|
|
5
|
+
|
|
1
6
|
<template>
|
|
2
|
-
<
|
|
7
|
+
<VaspErrorBoundary>
|
|
8
|
+
<RouterView />
|
|
9
|
+
</VaspErrorBoundary>
|
|
10
|
+
<VaspNotifications />
|
|
3
11
|
</template>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { onErrorCaptured, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
const hasError = ref(false)
|
|
5
|
+
const message = ref('Something went wrong')
|
|
6
|
+
|
|
7
|
+
onErrorCaptured((error) => {
|
|
8
|
+
hasError.value = true
|
|
9
|
+
message.value = import.meta.env.DEV && error instanceof Error
|
|
10
|
+
? error.message
|
|
11
|
+
: 'Something went wrong'
|
|
12
|
+
return false
|
|
13
|
+
})
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div v-if="hasError" class="vasp-error-boundary">
|
|
18
|
+
<h2>Unexpected error</h2>
|
|
19
|
+
<p>{{ message }}</p>
|
|
20
|
+
</div>
|
|
21
|
+
<slot v-else />
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<style scoped>
|
|
25
|
+
.vasp-error-boundary {
|
|
26
|
+
margin: 24px;
|
|
27
|
+
padding: 16px;
|
|
28
|
+
border: 1px solid #ef4444;
|
|
29
|
+
border-radius: 8px;
|
|
30
|
+
background: #fef2f2;
|
|
31
|
+
color: #991b1b;
|
|
32
|
+
}
|
|
33
|
+
</style>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { notifications, removeNotification } from '../vasp/useVaspNotifications.js'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<div class="vasp-notifications">
|
|
7
|
+
<div
|
|
8
|
+
v-for="item in notifications"
|
|
9
|
+
:key="item.id"
|
|
10
|
+
class="vasp-notification"
|
|
11
|
+
:class="`is-${item.type}`"
|
|
12
|
+
@click="removeNotification(item.id)"
|
|
13
|
+
>
|
|
14
|
+
{{ item.message }}
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<style scoped>
|
|
20
|
+
.vasp-notifications {
|
|
21
|
+
position: fixed;
|
|
22
|
+
top: 16px;
|
|
23
|
+
right: 16px;
|
|
24
|
+
z-index: 1000;
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
gap: 8px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.vasp-notification {
|
|
31
|
+
min-width: 220px;
|
|
32
|
+
padding: 10px 12px;
|
|
33
|
+
border-radius: 8px;
|
|
34
|
+
color: #111827;
|
|
35
|
+
background: #e5e7eb;
|
|
36
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
animation: slide-in 0.2s ease-out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.vasp-notification.is-error {
|
|
42
|
+
background: #fee2e2;
|
|
43
|
+
color: #991b1b;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.vasp-notification.is-success {
|
|
47
|
+
background: #dcfce7;
|
|
48
|
+
color: #166534;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.vasp-notification.is-info {
|
|
52
|
+
background: #dbeafe;
|
|
53
|
+
color: #1e40af;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@keyframes slide-in {
|
|
57
|
+
from { transform: translateX(20px); opacity: 0; }
|
|
58
|
+
to { transform: translateX(0); opacity: 1; }
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ref } from 'vue'
|
|
2
2
|
import { $fetch } from 'ofetch'
|
|
3
|
+
import { notifyError } from './useVaspNotifications.js'
|
|
3
4
|
|
|
4
5
|
const user = ref(null)
|
|
5
6
|
let checked = false
|
|
@@ -18,27 +19,42 @@ export function useAuth() {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
async function login(username, password) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
try {
|
|
23
|
+
user.value = await $fetch(`${API}/auth/login`, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
body: { username, password },
|
|
26
|
+
credentials: 'include',
|
|
27
|
+
})
|
|
28
|
+
return user.value
|
|
29
|
+
} catch (error) {
|
|
30
|
+
notifyError(error)
|
|
31
|
+
throw error
|
|
32
|
+
}
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
async function register(username, password, email) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
try {
|
|
37
|
+
user.value = await $fetch(`${API}/auth/register`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
body: { username, password, email },
|
|
40
|
+
credentials: 'include',
|
|
41
|
+
})
|
|
42
|
+
return user.value
|
|
43
|
+
} catch (error) {
|
|
44
|
+
notifyError(error)
|
|
45
|
+
throw error
|
|
46
|
+
}
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
async function logout() {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
try {
|
|
51
|
+
await $fetch(`${API}/auth/logout`, { method: 'POST', credentials: 'include' })
|
|
52
|
+
user.value = null
|
|
53
|
+
checked = false
|
|
54
|
+
} catch (error) {
|
|
55
|
+
notifyError(error)
|
|
56
|
+
throw error
|
|
57
|
+
}
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
return { user, checkAuth, login, register, logout }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Auto-generated by Vasp — do not edit
|
|
2
2
|
import { useVasp } from '@vasp-framework/runtime'
|
|
3
|
+
import { notifyError } from '../useVaspNotifications.js'
|
|
3
4
|
|
|
4
5
|
{{#each actions}}
|
|
5
6
|
/**
|
|
@@ -9,7 +10,12 @@ import { useVasp } from '@vasp-framework/runtime'
|
|
|
9
10
|
*/
|
|
10
11
|
export async function {{camelCase name}}(args) {
|
|
11
12
|
const { $vasp } = useVasp()
|
|
12
|
-
|
|
13
|
+
try {
|
|
14
|
+
return await $vasp.action('{{camelCase name}}', args)
|
|
15
|
+
} catch (error) {
|
|
16
|
+
notifyError(error)
|
|
17
|
+
throw error
|
|
18
|
+
}
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
{{/each}}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
// Auto-generated by Vasp — do not edit
|
|
2
2
|
import { $fetch } from 'ofetch'
|
|
3
|
+
import { safeParse } from 'valibot'
|
|
4
|
+
import { ref } from 'vue'
|
|
5
|
+
import {
|
|
6
|
+
{{#each cruds}}
|
|
7
|
+
Create{{pascalCase entity}}Schema,
|
|
8
|
+
Update{{pascalCase entity}}Schema,
|
|
9
|
+
{{/each}}
|
|
10
|
+
} from '@shared/validation.js'
|
|
11
|
+
import { notifyError } from '../useVaspNotifications.js'
|
|
3
12
|
|
|
4
13
|
const API = import.meta.env.VITE_API_URL || '/api'
|
|
5
14
|
|
|
@@ -9,20 +18,100 @@ const API = import.meta.env.VITE_API_URL || '/api'
|
|
|
9
18
|
*/
|
|
10
19
|
export function use{{pascalCase entity}}Crud() {
|
|
11
20
|
const base = `${API}/crud/{{camelCase entity}}`
|
|
21
|
+
const loading = ref(false)
|
|
22
|
+
const error = ref(null)
|
|
23
|
+
|
|
24
|
+
const fail = (err) => {
|
|
25
|
+
error.value = err instanceof Error ? err.message : 'Request failed'
|
|
26
|
+
notifyError(err)
|
|
27
|
+
}
|
|
12
28
|
|
|
13
29
|
return {
|
|
30
|
+
loading,
|
|
31
|
+
error,
|
|
14
32
|
{{#if (includes operations "list")}}
|
|
15
|
-
|
|
33
|
+
list: async () => {
|
|
34
|
+
loading.value = true
|
|
35
|
+
error.value = null
|
|
36
|
+
try {
|
|
37
|
+
return await $fetch(base, { credentials: 'include' })
|
|
38
|
+
} catch (err) {
|
|
39
|
+
fail(err)
|
|
40
|
+
throw err
|
|
41
|
+
} finally {
|
|
42
|
+
loading.value = false
|
|
43
|
+
}
|
|
44
|
+
},
|
|
16
45
|
{{/if}}
|
|
17
46
|
{{#if (includes operations "create")}}
|
|
18
|
-
|
|
47
|
+
create: async (data) => {
|
|
48
|
+
const parsed = safeParse(Create{{pascalCase entity}}Schema, data)
|
|
49
|
+
if (!parsed.success) {
|
|
50
|
+
const msg = parsed.issues?.[0]?.message ?? 'Invalid input'
|
|
51
|
+
const err = new Error(msg)
|
|
52
|
+
fail(err)
|
|
53
|
+
throw err
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
loading.value = true
|
|
57
|
+
error.value = null
|
|
58
|
+
try {
|
|
59
|
+
return await $fetch(base, { method: 'POST', body: parsed.output, credentials: 'include' })
|
|
60
|
+
} catch (err) {
|
|
61
|
+
fail(err)
|
|
62
|
+
throw err
|
|
63
|
+
} finally {
|
|
64
|
+
loading.value = false
|
|
65
|
+
}
|
|
66
|
+
},
|
|
19
67
|
{{/if}}
|
|
20
|
-
|
|
68
|
+
get: async (id) => {
|
|
69
|
+
loading.value = true
|
|
70
|
+
error.value = null
|
|
71
|
+
try {
|
|
72
|
+
return await $fetch(`${base}/${id}`, { credentials: 'include' })
|
|
73
|
+
} catch (err) {
|
|
74
|
+
fail(err)
|
|
75
|
+
throw err
|
|
76
|
+
} finally {
|
|
77
|
+
loading.value = false
|
|
78
|
+
}
|
|
79
|
+
},
|
|
21
80
|
{{#if (includes operations "update")}}
|
|
22
|
-
|
|
81
|
+
update: async (id, data) => {
|
|
82
|
+
const parsed = safeParse(Update{{pascalCase entity}}Schema, data)
|
|
83
|
+
if (!parsed.success) {
|
|
84
|
+
const msg = parsed.issues?.[0]?.message ?? 'Invalid input'
|
|
85
|
+
const err = new Error(msg)
|
|
86
|
+
fail(err)
|
|
87
|
+
throw err
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
loading.value = true
|
|
91
|
+
error.value = null
|
|
92
|
+
try {
|
|
93
|
+
return await $fetch(`${base}/${id}`, { method: 'PUT', body: parsed.output, credentials: 'include' })
|
|
94
|
+
} catch (err) {
|
|
95
|
+
fail(err)
|
|
96
|
+
throw err
|
|
97
|
+
} finally {
|
|
98
|
+
loading.value = false
|
|
99
|
+
}
|
|
100
|
+
},
|
|
23
101
|
{{/if}}
|
|
24
102
|
{{#if (includes operations "delete")}}
|
|
25
|
-
|
|
103
|
+
remove: async (id) => {
|
|
104
|
+
loading.value = true
|
|
105
|
+
error.value = null
|
|
106
|
+
try {
|
|
107
|
+
return await $fetch(`${base}/${id}`, { method: 'DELETE', credentials: 'include' })
|
|
108
|
+
} catch (err) {
|
|
109
|
+
fail(err)
|
|
110
|
+
throw err
|
|
111
|
+
} finally {
|
|
112
|
+
loading.value = false
|
|
113
|
+
}
|
|
114
|
+
},
|
|
26
115
|
{{/if}}
|
|
27
116
|
}
|
|
28
117
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export const notifications = ref([])
|
|
4
|
+
|
|
5
|
+
export function pushNotification(type, message, timeoutMs = 3500) {
|
|
6
|
+
const id = Date.now() + Math.floor(Math.random() * 1000)
|
|
7
|
+
notifications.value.push({ id, type, message })
|
|
8
|
+
|
|
9
|
+
if (timeoutMs > 0) {
|
|
10
|
+
setTimeout(() => removeNotification(id), timeoutMs)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return id
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function removeNotification(id) {
|
|
17
|
+
notifications.value = notifications.value.filter((item) => item.id !== id)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function notifyError(error) {
|
|
21
|
+
if (error instanceof Error && error.message) {
|
|
22
|
+
pushNotification('error', error.message)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
pushNotification('error', 'Request failed')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useVaspNotifications() {
|
|
29
|
+
return {
|
|
30
|
+
notifications,
|
|
31
|
+
pushNotification,
|
|
32
|
+
removeNotification,
|
|
33
|
+
notifyError,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -7,6 +7,7 @@ export default defineConfig({
|
|
|
7
7
|
resolve: {
|
|
8
8
|
alias: {
|
|
9
9
|
'@src': fileURLToPath(new URL('./src', import.meta.url)),
|
|
10
|
+
'@shared': fileURLToPath(new URL('./shared', import.meta.url)),
|
|
10
11
|
'@vasp-framework/client': fileURLToPath(new URL('./src/vasp/client', import.meta.url)),
|
|
11
12
|
},
|
|
12
13
|
},
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import VaspErrorBoundary from './components/VaspErrorBoundary.vue'
|
|
3
|
+
import VaspNotifications from './components/VaspNotifications.vue'
|
|
4
|
+
</script>
|
|
5
|
+
|
|
1
6
|
<template>
|
|
2
|
-
<
|
|
7
|
+
<VaspErrorBoundary>
|
|
8
|
+
<RouterView />
|
|
9
|
+
</VaspErrorBoundary>
|
|
10
|
+
<VaspNotifications />
|
|
3
11
|
</template>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onErrorCaptured, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
const hasError = ref(false)
|
|
5
|
+
const message = ref('Something went wrong')
|
|
6
|
+
|
|
7
|
+
onErrorCaptured((error) => {
|
|
8
|
+
hasError.value = true
|
|
9
|
+
message.value = import.meta.env.DEV && error instanceof Error
|
|
10
|
+
? error.message
|
|
11
|
+
: 'Something went wrong'
|
|
12
|
+
return false
|
|
13
|
+
})
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div v-if="hasError" class="vasp-error-boundary">
|
|
18
|
+
<h2>Unexpected error</h2>
|
|
19
|
+
<p>{{ message }}</p>
|
|
20
|
+
</div>
|
|
21
|
+
<slot v-else />
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<style scoped>
|
|
25
|
+
.vasp-error-boundary {
|
|
26
|
+
margin: 24px;
|
|
27
|
+
padding: 16px;
|
|
28
|
+
border: 1px solid #ef4444;
|
|
29
|
+
border-radius: 8px;
|
|
30
|
+
background: #fef2f2;
|
|
31
|
+
color: #991b1b;
|
|
32
|
+
}
|
|
33
|
+
</style>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { notifications, removeNotification } from '../vasp/useVaspNotifications.js'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<div class="vasp-notifications">
|
|
7
|
+
<div
|
|
8
|
+
v-for="item in notifications"
|
|
9
|
+
:key="item.id"
|
|
10
|
+
class="vasp-notification"
|
|
11
|
+
:class="`is-${item.type}`"
|
|
12
|
+
@click="removeNotification(item.id)"
|
|
13
|
+
>
|
|
14
|
+
{{ item.message }}
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<style scoped>
|
|
20
|
+
.vasp-notifications {
|
|
21
|
+
position: fixed;
|
|
22
|
+
top: 16px;
|
|
23
|
+
right: 16px;
|
|
24
|
+
z-index: 1000;
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
gap: 8px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.vasp-notification {
|
|
31
|
+
min-width: 220px;
|
|
32
|
+
padding: 10px 12px;
|
|
33
|
+
border-radius: 8px;
|
|
34
|
+
color: #111827;
|
|
35
|
+
background: #e5e7eb;
|
|
36
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
animation: slide-in 0.2s ease-out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.vasp-notification.is-error {
|
|
42
|
+
background: #fee2e2;
|
|
43
|
+
color: #991b1b;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.vasp-notification.is-success {
|
|
47
|
+
background: #dcfce7;
|
|
48
|
+
color: #166534;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.vasp-notification.is-info {
|
|
52
|
+
background: #dbeafe;
|
|
53
|
+
color: #1e40af;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@keyframes slide-in {
|
|
57
|
+
from { transform: translateX(20px); opacity: 0; }
|
|
58
|
+
to { transform: translateX(0); opacity: 1; }
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ref, type Ref } from 'vue'
|
|
2
2
|
import { $fetch } from 'ofetch'
|
|
3
|
+
import { notifyError } from './useVaspNotifications.js'
|
|
3
4
|
|
|
4
5
|
export interface AuthUser {
|
|
5
6
|
id: number
|
|
@@ -26,27 +27,42 @@ export function useAuth() {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
async function login(username: string, password: string): Promise<AuthUser> {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
try {
|
|
31
|
+
user.value = await $fetch<AuthUser>(`${API}/auth/login`, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
body: { username, password },
|
|
34
|
+
credentials: 'include',
|
|
35
|
+
})
|
|
36
|
+
return user.value!
|
|
37
|
+
} catch (error) {
|
|
38
|
+
notifyError(error)
|
|
39
|
+
throw error
|
|
40
|
+
}
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
async function register(username: string, password: string, email?: string): Promise<AuthUser> {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
try {
|
|
45
|
+
user.value = await $fetch<AuthUser>(`${API}/auth/register`, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
body: { username, password, email },
|
|
48
|
+
credentials: 'include',
|
|
49
|
+
})
|
|
50
|
+
return user.value!
|
|
51
|
+
} catch (error) {
|
|
52
|
+
notifyError(error)
|
|
53
|
+
throw error
|
|
54
|
+
}
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
async function logout(): Promise<void> {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
try {
|
|
59
|
+
await $fetch(`${API}/auth/logout`, { method: 'POST', credentials: 'include' })
|
|
60
|
+
user.value = null
|
|
61
|
+
checked = false
|
|
62
|
+
} catch (error) {
|
|
63
|
+
notifyError(error)
|
|
64
|
+
throw error
|
|
65
|
+
}
|
|
50
66
|
}
|
|
51
67
|
|
|
52
68
|
return { user, checkAuth, login, register, logout }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Auto-generated by Vasp — do not edit
|
|
2
2
|
import { useVasp } from '@vasp-framework/runtime'
|
|
3
|
+
import { notifyError } from '../useVaspNotifications.js'
|
|
3
4
|
import type {
|
|
4
5
|
{{#each actions}}
|
|
5
6
|
{{pascalCase name}}Args,
|
|
@@ -13,7 +14,12 @@ import type {
|
|
|
13
14
|
*/
|
|
14
15
|
export async function {{camelCase name}}(args: {{pascalCase name}}Args): Promise<{{pascalCase name}}Return> {
|
|
15
16
|
const { $vasp } = useVasp()
|
|
16
|
-
|
|
17
|
+
try {
|
|
18
|
+
return await $vasp.action('{{camelCase name}}', args) as Promise<{{pascalCase name}}Return>
|
|
19
|
+
} catch (error) {
|
|
20
|
+
notifyError(error)
|
|
21
|
+
throw error
|
|
22
|
+
}
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
{{/each}}
|