@wishbone-media/spark 0.13.1 → 0.14.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wishbone-media/spark",
3
- "version": "0.13.1",
3
+ "version": "0.14.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -220,7 +220,11 @@ const toggleAppSelector = () => {
220
220
  slotProps.footerSlot = props.appSelectorSlots.footerSlot
221
221
  }
222
222
 
223
- sparkOverlayService.showRight(SparkAppSelector, slotProps)
223
+ sparkOverlayService.showRight(SparkAppSelector, slotProps, {
224
+ select: (brand) => {
225
+ sparkOverlayService.closeRight()
226
+ },
227
+ })
224
228
  }
225
229
 
226
230
  const toggleBrandSelector = () => {
@@ -230,6 +234,7 @@ const toggleBrandSelector = () => {
230
234
  {
231
235
  select: (brand) => {
232
236
  sparkBrandFilterStore.toggleBrand(brand)
237
+ sparkOverlayService.closeLeft()
233
238
  },
234
239
  },
235
240
  )
@@ -0,0 +1,91 @@
1
+ // Global registry for bootstrap reset callbacks
2
+ const resetCallbacks = []
3
+
4
+ /**
5
+ * Register a reset callback to be called on logout
6
+ * This is used internally by createBootstrapService
7
+ * @private
8
+ */
9
+ function registerResetCallback(callback) {
10
+ resetCallbacks.push(callback)
11
+ }
12
+
13
+ /**
14
+ * Reset all registered bootstrap services
15
+ * Called automatically by the auth store on logout
16
+ * @internal
17
+ */
18
+ export function resetAllBootstrapServices() {
19
+ resetCallbacks.forEach(callback => callback())
20
+ }
21
+
22
+ /**
23
+ * Creates a bootstrap service for initializing app-level data after authentication
24
+ *
25
+ * This factory function provides a standardized pattern for loading user-dependent data
26
+ * in authenticated SPAs. It ensures initialization runs only once per session and provides
27
+ * automatic cleanup on logout (no manual reset needed).
28
+ *
29
+ * @param {Function} initFn - Async function that performs the initialization logic
30
+ * (e.g., loading stores, fetching user data)
31
+ * @returns {Object} Object containing bootstrapApp function
32
+ *
33
+ * @example
34
+ * // In consumer app: src/bootstrap.js
35
+ * import { createBootstrapService } from '@wishbone-media/spark'
36
+ * import { useGlobalStore } from '@/stores/global.js'
37
+ * import { useBrandFilterStore } from '@/stores/brand-filter.js'
38
+ *
39
+ * export const { bootstrapApp } = createBootstrapService(async () => {
40
+ * const globalStore = useGlobalStore()
41
+ * const brandStore = useBrandFilterStore()
42
+ *
43
+ * // Load stores in parallel for better performance
44
+ * await Promise.all([
45
+ * globalStore.loadGlobalData(),
46
+ * brandStore.loadBrands(),
47
+ * ])
48
+ * })
49
+ */
50
+ export function createBootstrapService(initFn) {
51
+ let initialized = false
52
+
53
+ /**
54
+ * Reset the bootstrap state
55
+ * Called automatically by Spark's auth store on logout
56
+ * @private
57
+ */
58
+ const resetBootstrap = () => {
59
+ initialized = false
60
+ }
61
+
62
+ // Register this service's reset callback
63
+ registerResetCallback(resetBootstrap)
64
+
65
+ /**
66
+ * Initialize application-level data after authentication
67
+ * Called automatically by the router guard when navigating to authenticated routes
68
+ *
69
+ * @returns {Promise<void>}
70
+ */
71
+ const bootstrapApp = async () => {
72
+ // Only run once per session
73
+ if (initialized) {
74
+ return
75
+ }
76
+
77
+ try {
78
+ // Execute the consumer-provided initialization logic
79
+ await initFn()
80
+ initialized = true
81
+ } catch (error) {
82
+ console.error('Error during app bootstrap:', error)
83
+ // Don't set initialized = true on error, allow retry on next navigation
84
+ throw error
85
+ }
86
+ }
87
+
88
+ return {
89
+ bootstrapApp,
90
+ }
91
+ }
@@ -81,6 +81,9 @@ export function createAxiosInstance(config = {}) {
81
81
  return instance
82
82
  }
83
83
 
84
+ // Module-level axios instance reference for consumer apps
85
+ let axiosInstance = null
86
+
84
87
  /**
85
88
  * Setup axios for a Vue app with global availability
86
89
  * @param {App} app - Vue app instance
@@ -88,7 +91,7 @@ export function createAxiosInstance(config = {}) {
88
91
  * @returns {AxiosInstance} Configured axios instance
89
92
  */
90
93
  export function setupAxios(app, config = {}) {
91
- const axiosInstance = createAxiosInstance(config)
94
+ axiosInstance = createAxiosInstance(config)
92
95
 
93
96
  // Make axios available via injection (Composition API)
94
97
  app.provide('axios', axiosInstance)
@@ -98,3 +101,41 @@ export function setupAxios(app, config = {}) {
98
101
 
99
102
  return axiosInstance
100
103
  }
104
+
105
+ /**
106
+ * Get the configured axios instance
107
+ * This allows consumer apps to access the axios instance in non-component contexts
108
+ * (like Pinia stores) without needing to pass it as a parameter.
109
+ *
110
+ * @returns {AxiosInstance} The axios instance
111
+ * @throws {Error} If axios has not been initialized via setupAxios()
112
+ *
113
+ * @example
114
+ * // In consumer app: src/plugins/axios.js
115
+ * import { setupAxios, getAxiosInstance as getSparkAxiosInstance } from '@wishbone-media/spark'
116
+ *
117
+ * export function setupAppAxios(app) {
118
+ * return setupAxios(app, {
119
+ * baseURL: import.meta.env.VITE_APP_API_URL,
120
+ * })
121
+ * }
122
+ *
123
+ * // Re-export for convenience
124
+ * export const getAxiosInstance = getSparkAxiosInstance
125
+ *
126
+ * // In stores: src/stores/global.js
127
+ * import { getAxiosInstance } from '@/plugins/axios'
128
+ *
129
+ * export const useGlobalStore = defineStore('global', () => {
130
+ * const loadData = async () => {
131
+ * const axios = getAxiosInstance()
132
+ * const { data } = await axios.get('/data')
133
+ * }
134
+ * })
135
+ */
136
+ export function getAxiosInstance() {
137
+ if (!axiosInstance) {
138
+ throw new Error('Axios instance not initialized. Call setupAxios() first.')
139
+ }
140
+ return axiosInstance
141
+ }
@@ -1,3 +1,4 @@
1
1
  export { Icons, addIcons, setupFontAwesome } from './fontawesome.js'
2
- export { createAuthRoutes, setupAuthGuards, create403Route, create404Route } from './router.js'
3
- export { createAxiosInstance, setupAxios } from './axios.js'
2
+ export { createAuthRoutes, setupAuthGuards, create403Route, create404Route, setupBootstrapGuard } from './router.js'
3
+ export { createAxiosInstance, setupAxios, getAxiosInstance } from './axios.js'
4
+ export { createBootstrapService } from './app-bootstrap.js'
@@ -178,3 +178,37 @@ export function create404Route(options = {}) {
178
178
  meta: { auth: false },
179
179
  }
180
180
  }
181
+
182
+ /**
183
+ * Setup bootstrap guard to initialize app data after authentication
184
+ * This guard waits for authentication to be ready, then calls the provided bootstrap function
185
+ * to load user-dependent data before rendering authenticated routes.
186
+ *
187
+ * IMPORTANT: This MUST be called AFTER setupAuthGuards() to ensure auth is ready first
188
+ *
189
+ * @param {Router} router - Vue router instance
190
+ * @param {Function} bootstrapFn - Async function that initializes app data
191
+ *
192
+ * @example
193
+ * import { setupAuthGuards, setupBootstrapGuard } from '@wishbone-media/spark'
194
+ * import { bootstrapApp } from '@/services/app-bootstrap'
195
+ *
196
+ * // Setup auth guards first
197
+ * setupAuthGuards(router)
198
+ *
199
+ * // Then setup bootstrap guard
200
+ * setupBootstrapGuard(router, bootstrapApp)
201
+ */
202
+ export function setupBootstrapGuard(router, bootstrapFn) {
203
+ router.beforeResolve(async (to) => {
204
+ // Only bootstrap for authenticated routes
205
+ if (to.meta.auth !== false) {
206
+ const authStore = useSparkAuthStore()
207
+
208
+ // Wait for auth to be ready and bootstrap if authenticated
209
+ if (authStore.state.ready && authStore.check) {
210
+ await bootstrapFn()
211
+ }
212
+ }
213
+ })
214
+ }
@@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
2
2
  import { reactive, computed } from 'vue'
3
3
  import axios from 'axios'
4
4
  import { getCookie, setCookie, deleteCookie } from '../utils/cookies.js'
5
+ import { resetAllBootstrapServices } from '../plugins/app-bootstrap.js'
5
6
 
6
7
  const TOKEN_NAME = 'bolt-next-token'
7
8
 
@@ -117,6 +118,9 @@ export const useSparkAuthStore = defineStore('auth', () => {
117
118
  } finally {
118
119
  clearTokenCookie()
119
120
  state.user = null
121
+
122
+ // Reset all bootstrap services to allow re-initialization on next login
123
+ resetAllBootstrapServices()
120
124
  }
121
125
  }
122
126