@userfrosting/sprinkle-core 6.0.0-alpha.5 → 6.0.0-alpha.6
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/app/assets/composables/index.ts +2 -1
- package/app/assets/composables/useAxiosInterceptor.ts +30 -0
- package/app/assets/composables/useCsrf.ts +10 -1
- package/app/assets/composables/{sprunjer.ts → useSprunjer.ts} +10 -3
- package/app/assets/index.ts +8 -0
- package/app/assets/interfaces/ApiResponse.ts +8 -1
- package/app/assets/interfaces/index.ts +1 -1
- package/app/assets/interfaces/sprunjer.ts +2 -1
- package/app/assets/routes/index.ts +4 -4
- package/app/assets/stores/index.ts +2 -1
- package/app/assets/stores/useAlertsStore.ts +30 -0
- package/app/assets/tests/composables/useCsrf.test.ts +2 -2
- package/app/assets/tests/plugin.test.ts +2 -2
- package/app/assets/tests/stores/config.test.ts +1 -1
- package/package.json +2 -2
- /package/app/assets/stores/{config.ts → useConfigStore.ts} +0 -0
- /package/app/assets/views/{401Unauthorized.vue → Page401Unauthorized.vue} +0 -0
- /package/app/assets/views/{403Forbidden.vue → Page403Forbidden.vue} +0 -0
- /package/app/assets/views/{404NotFound.vue → Page404NotFound.vue} +0 -0
- /package/app/assets/views/{ErrorPage.vue → PageError.vue} +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
import { useAlertsStore } from '../stores/useAlertsStore'
|
|
3
|
+
import { Severity } from '../interfaces'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Axios Error Handler
|
|
7
|
+
*
|
|
8
|
+
* This composable sets up an Axios interceptor to handle errors globally using
|
|
9
|
+
* the Alerts store. It sends the error to the Alerts store, unless it's a 401
|
|
10
|
+
* error.
|
|
11
|
+
*
|
|
12
|
+
* @see https://axios-http.com/docs/interceptors
|
|
13
|
+
*/
|
|
14
|
+
export const useAxiosInterceptor = () => {
|
|
15
|
+
axios.interceptors.response.use(
|
|
16
|
+
(response) => response,
|
|
17
|
+
(error) => {
|
|
18
|
+
if (error.response.status !== 401) {
|
|
19
|
+
const alertStore = useAlertsStore()
|
|
20
|
+
alertStore.push({
|
|
21
|
+
title: error.response.data.title ?? error.response?.statusText,
|
|
22
|
+
description: error.response?.data?.description ?? error.message,
|
|
23
|
+
style: Severity.Danger
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return Promise.reject(error)
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -34,6 +34,14 @@ export const useCsrf = () => {
|
|
|
34
34
|
axios.defaults.headers.patch[key_value.value] = token.value
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Fetch the CSRF token from the server
|
|
39
|
+
*/
|
|
40
|
+
async function fetchCsrfToken() {
|
|
41
|
+
const response = await axios.get('/api/csrf')
|
|
42
|
+
updateFromHeaders(response.headers)
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
/**
|
|
38
46
|
* Get the CSRF token name and value keys from config.
|
|
39
47
|
*/
|
|
@@ -115,6 +123,7 @@ export const useCsrf = () => {
|
|
|
115
123
|
name,
|
|
116
124
|
token,
|
|
117
125
|
isEnabled,
|
|
118
|
-
updateFromHeaders
|
|
126
|
+
updateFromHeaders,
|
|
127
|
+
fetchCsrfToken
|
|
119
128
|
}
|
|
120
129
|
}
|
|
@@ -33,7 +33,13 @@
|
|
|
33
33
|
*/
|
|
34
34
|
import { ref, toValue, watchEffect, computed } from 'vue'
|
|
35
35
|
import axios from 'axios'
|
|
36
|
-
import type {
|
|
36
|
+
import type {
|
|
37
|
+
ApiErrorResponse,
|
|
38
|
+
AssociativeArray,
|
|
39
|
+
Sprunjer,
|
|
40
|
+
SprunjerData,
|
|
41
|
+
SprunjerResponse
|
|
42
|
+
} from '../interfaces'
|
|
37
43
|
|
|
38
44
|
export const useSprunjer = (
|
|
39
45
|
dataUrl: string | (() => string),
|
|
@@ -60,6 +66,7 @@ export const useSprunjer = (
|
|
|
60
66
|
|
|
61
67
|
// State
|
|
62
68
|
const loading = ref<boolean>(false)
|
|
69
|
+
const error = ref<ApiErrorResponse | null>(null)
|
|
63
70
|
|
|
64
71
|
/**
|
|
65
72
|
* Api fetch function
|
|
@@ -87,8 +94,7 @@ export const useSprunjer = (
|
|
|
87
94
|
data.value.filterable = response.data.filterable ?? []
|
|
88
95
|
})
|
|
89
96
|
.catch((err) => {
|
|
90
|
-
|
|
91
|
-
console.error(err)
|
|
97
|
+
error.value = err.response.data as ApiErrorResponse
|
|
92
98
|
})
|
|
93
99
|
.finally(() => {
|
|
94
100
|
loading.value = false
|
|
@@ -169,6 +175,7 @@ export const useSprunjer = (
|
|
|
169
175
|
data,
|
|
170
176
|
fetch,
|
|
171
177
|
loading,
|
|
178
|
+
error,
|
|
172
179
|
downloadCsv,
|
|
173
180
|
totalPages,
|
|
174
181
|
countFiltered,
|
package/app/assets/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { App } from 'vue'
|
|
2
2
|
import { useConfigStore, useTranslator } from './stores'
|
|
3
3
|
import { useCsrf } from './composables/useCsrf'
|
|
4
|
+
import { useAxiosInterceptor } from './composables'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Core Sprinkle initialization recipe.
|
|
@@ -11,6 +12,13 @@ import { useCsrf } from './composables/useCsrf'
|
|
|
11
12
|
*/
|
|
12
13
|
export default {
|
|
13
14
|
install: (app: App) => {
|
|
15
|
+
/**
|
|
16
|
+
* Add Axios error handler.
|
|
17
|
+
* Load first to ensure that all axios requests are intercepted.
|
|
18
|
+
* This is important for the config loading and translator loading.
|
|
19
|
+
*/
|
|
20
|
+
useAxiosInterceptor()
|
|
21
|
+
|
|
14
22
|
/**
|
|
15
23
|
* Load configuration
|
|
16
24
|
*/
|
|
@@ -27,4 +27,4 @@ export type { SprunjerRequest, SprunjerResponse } from './sprunjerApi'
|
|
|
27
27
|
export type { DictionaryResponse, DictionaryEntries, DictionaryConfig } from './DictionaryApi'
|
|
28
28
|
|
|
29
29
|
// Misc
|
|
30
|
-
export type { ApiResponse } from './ApiResponse'
|
|
30
|
+
export type { ApiResponse, ApiErrorResponse } from './ApiResponse'
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Represents the interface for the Sprunjer composable.
|
|
5
5
|
*/
|
|
6
6
|
import type { Ref, ComputedRef } from 'vue'
|
|
7
|
-
import type { AssociativeArray } from '.'
|
|
7
|
+
import type { ApiErrorResponse, AssociativeArray } from '.'
|
|
8
8
|
|
|
9
9
|
export interface Sprunjer {
|
|
10
10
|
dataUrl: string | (() => string)
|
|
@@ -15,6 +15,7 @@ export interface Sprunjer {
|
|
|
15
15
|
data: Ref<SprunjerData>
|
|
16
16
|
fetch: () => void
|
|
17
17
|
loading: Ref<boolean>
|
|
18
|
+
error: Ref<ApiErrorResponse | null>
|
|
18
19
|
totalPages: ComputedRef<number>
|
|
19
20
|
downloadCsv: () => void
|
|
20
21
|
countFiltered: ComputedRef<number>
|
|
@@ -12,7 +12,7 @@ export default [
|
|
|
12
12
|
title: 'ERROR.404.TITLE',
|
|
13
13
|
description: 'ERROR.404.DESCRIPTION'
|
|
14
14
|
},
|
|
15
|
-
component: () => import('../views/
|
|
15
|
+
component: () => import('../views/Page404NotFound.vue')
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
path: '/:pathMatch(.*)*',
|
|
@@ -21,7 +21,7 @@ export default [
|
|
|
21
21
|
title: 'ERROR.401.TITLE',
|
|
22
22
|
description: 'ERROR.401.DESCRIPTION'
|
|
23
23
|
},
|
|
24
|
-
component: () => import('../views/
|
|
24
|
+
component: () => import('../views/Page401Unauthorized.vue')
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
path: '/:pathMatch(.*)*',
|
|
@@ -30,7 +30,7 @@ export default [
|
|
|
30
30
|
title: 'ERROR.403.TITLE',
|
|
31
31
|
description: 'ERROR.403.DESCRIPTION'
|
|
32
32
|
},
|
|
33
|
-
component: () => import('../views/
|
|
33
|
+
component: () => import('../views/Page403Forbidden.vue')
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
path: '/:pathMatch(.*)*',
|
|
@@ -39,6 +39,6 @@ export default [
|
|
|
39
39
|
title: 'ERROR.TITLE',
|
|
40
40
|
description: 'ERROR.DESCRIPTION'
|
|
41
41
|
},
|
|
42
|
-
component: () => import('../views/
|
|
42
|
+
component: () => import('../views/PageError.vue')
|
|
43
43
|
}
|
|
44
44
|
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import type { AlertInterface } from '../interfaces'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Alerts Store
|
|
6
|
+
*
|
|
7
|
+
* Manages application alerts in a reactive store. Templates can use this
|
|
8
|
+
* store to display alerts to users, including errors, warnings,
|
|
9
|
+
* informational, or success messages. When an alert is added, templates
|
|
10
|
+
* should automatically update the interface to reflect the change.
|
|
11
|
+
*/
|
|
12
|
+
export const useAlertsStore = defineStore('alerts', {
|
|
13
|
+
state: () => ({
|
|
14
|
+
alerts: [] as AlertInterface[]
|
|
15
|
+
}),
|
|
16
|
+
actions: {
|
|
17
|
+
push(alert: AlertInterface) {
|
|
18
|
+
this.alerts.push(alert)
|
|
19
|
+
},
|
|
20
|
+
pop() {
|
|
21
|
+
return this.alerts.pop()
|
|
22
|
+
},
|
|
23
|
+
shift() {
|
|
24
|
+
return this.alerts.shift()
|
|
25
|
+
},
|
|
26
|
+
clear() {
|
|
27
|
+
this.alerts = []
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
})
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, expect, beforeEach, afterEach, test, vi } from 'vitest'
|
|
2
2
|
import axios from 'axios'
|
|
3
|
-
import { useConfigStore } from '../../stores/
|
|
3
|
+
import { useConfigStore } from '../../stores/useConfigStore'
|
|
4
4
|
import { useCsrf } from '../../composables/useCsrf'
|
|
5
5
|
import { nextTick } from 'vue'
|
|
6
6
|
|
|
7
7
|
// Mock the config store
|
|
8
|
-
vi.mock('../../stores/
|
|
8
|
+
vi.mock('../../stores/useConfigStore')
|
|
9
9
|
const mockUseConfigStore = {
|
|
10
10
|
get: vi.fn()
|
|
11
11
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test, vi } from 'vitest'
|
|
2
2
|
import { createApp } from 'vue'
|
|
3
|
-
import { useConfigStore } from '../stores/
|
|
3
|
+
import { useConfigStore } from '../stores/useConfigStore'
|
|
4
4
|
import plugin from '..'
|
|
5
|
-
import * as Config from '../stores/
|
|
5
|
+
import * as Config from '../stores/useConfigStore'
|
|
6
6
|
import * as Translator from '../stores/useTranslator'
|
|
7
7
|
|
|
8
8
|
const mockConfigStore = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, beforeEach, test, vi } from 'vitest'
|
|
2
2
|
import { setActivePinia, createPinia } from 'pinia'
|
|
3
3
|
import axios from 'axios'
|
|
4
|
-
import { useConfigStore } from '../../stores/
|
|
4
|
+
import { useConfigStore } from '../../stores/useConfigStore'
|
|
5
5
|
|
|
6
6
|
const testConfig = {
|
|
7
7
|
name: 'Test Config',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@userfrosting/sprinkle-core",
|
|
3
|
-
"version": "6.0.0-alpha.
|
|
3
|
+
"version": "6.0.0-alpha.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Core Sprinkle for UserFrosting",
|
|
6
6
|
"funding": "https://opencollective.com/userfrosting",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"happy-dom": "^15.11.6",
|
|
60
60
|
"less": "^4.2.0",
|
|
61
61
|
"npm-run-all2": "^6.1.2",
|
|
62
|
-
"prettier": "^3.2
|
|
62
|
+
"prettier": "^3.6.2",
|
|
63
63
|
"vite": "^6.2",
|
|
64
64
|
"vite-plugin-dts": "^4.0.0",
|
|
65
65
|
"vitest": "^3.1.1",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|