@wishbone-media/spark 0.11.0 → 0.12.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/index.js +1391 -1319
- package/package.json +1 -1
- package/src/components/SparkAppSelector.vue +31 -55
- package/src/containers/SparkDefaultContainer.vue +30 -5
- package/src/plugins/router.js +2 -2
- package/src/stores/app-selector.js +140 -0
- package/src/stores/app.js +0 -2
- package/src/stores/index.js +1 -0
- package/src/views/SparkResetPasswordView.vue +1 -1
package/package.json
CHANGED
|
@@ -12,12 +12,14 @@
|
|
|
12
12
|
/>
|
|
13
13
|
</div>
|
|
14
14
|
</div>
|
|
15
|
-
<
|
|
15
|
+
<a
|
|
16
16
|
v-for="app in appsWithCurrent"
|
|
17
17
|
:key="app.name"
|
|
18
|
+
:href="app.href"
|
|
19
|
+
target="_blank"
|
|
18
20
|
:class="app.current ? 'bg-gray-50' : 'hover:bg-gray-50'"
|
|
19
21
|
class="flex px-[22px] py-[15px] cursor-pointer"
|
|
20
|
-
@click="
|
|
22
|
+
@click="app.current ? $event.preventDefault() : handleAppClick(app, $event)"
|
|
21
23
|
>
|
|
22
24
|
<div class="gap-y-1">
|
|
23
25
|
<div class="text-base text-gray-800 flex items-center">
|
|
@@ -38,12 +40,21 @@
|
|
|
38
40
|
class="h-5 w-5 shrink-0"
|
|
39
41
|
/>
|
|
40
42
|
</div>
|
|
41
|
-
</
|
|
43
|
+
</a>
|
|
42
44
|
<div></div>
|
|
43
45
|
</div>
|
|
44
46
|
<div class="mt-auto">
|
|
45
|
-
<div class="p-6">
|
|
46
|
-
|
|
47
|
+
<div v-if="$slots.bottom || bottomSlot" class="p-6">
|
|
48
|
+
<slot name="bottom">
|
|
49
|
+
<component v-if="bottomSlot" :is="bottomSlot" />
|
|
50
|
+
</slot>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div v-if="$slots.footer || footerSlot" class="bg-gray-50 p-6">
|
|
54
|
+
<slot name="footer">
|
|
55
|
+
<component v-if="footerSlot" :is="footerSlot" />
|
|
56
|
+
</slot>
|
|
57
|
+
</div>
|
|
47
58
|
</div>
|
|
48
59
|
</div>
|
|
49
60
|
</div>
|
|
@@ -52,69 +63,34 @@
|
|
|
52
63
|
<script setup>
|
|
53
64
|
import { computed } from 'vue'
|
|
54
65
|
import { Icons } from '@/plugins/fontawesome'
|
|
66
|
+
import { useSparkAppSelectorStore } from '@/stores/app-selector'
|
|
67
|
+
import { useSparkAppStore } from '@/stores/app'
|
|
55
68
|
|
|
56
69
|
const props = defineProps({
|
|
57
|
-
|
|
58
|
-
type:
|
|
59
|
-
default
|
|
60
|
-
return [
|
|
61
|
-
{
|
|
62
|
-
name: '3CX',
|
|
63
|
-
description: 'VOIP Phone',
|
|
64
|
-
href: 'https://3cx.letsbolt.com.au',
|
|
65
|
-
icon: 'farLaptopMobile',
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
name: 'Buzz',
|
|
69
|
-
description: 'Communication on the go',
|
|
70
|
-
href: 'https://buzz.letsbolt.com.au',
|
|
71
|
-
icon: 'farSatelliteDish',
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
name: 'Dash',
|
|
75
|
-
description: 'Financial powerhouse',
|
|
76
|
-
href: 'https://dash.letsbolt.com.au',
|
|
77
|
-
icon: 'farScaleBalanced',
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
name: 'MAPit',
|
|
81
|
-
description: 'Geolocation everything',
|
|
82
|
-
href: 'https://mapit.letsbolt.com.au',
|
|
83
|
-
icon: 'farStreetView',
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: 'ProspectR',
|
|
87
|
-
description: 'Leads management',
|
|
88
|
-
href: 'https://prospectr.letsbolt.com.au',
|
|
89
|
-
icon: 'farFaceSmileRelaxed',
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: 'ReVuze',
|
|
93
|
-
description: 'Get Customer feedback',
|
|
94
|
-
href: 'https://revuze.letsbolt.com.au',
|
|
95
|
-
icon: 'farComments',
|
|
96
|
-
},
|
|
97
|
-
]
|
|
98
|
-
},
|
|
70
|
+
bottomSlot: {
|
|
71
|
+
type: [Object, Function],
|
|
72
|
+
default: null,
|
|
99
73
|
},
|
|
100
|
-
|
|
101
|
-
type:
|
|
102
|
-
default:
|
|
74
|
+
footerSlot: {
|
|
75
|
+
type: [Object, Function],
|
|
76
|
+
default: null,
|
|
103
77
|
},
|
|
104
78
|
})
|
|
105
79
|
|
|
106
80
|
const emit = defineEmits(['close', 'select'])
|
|
107
81
|
|
|
82
|
+
const appSelectorStore = useSparkAppSelectorStore()
|
|
83
|
+
const appStore = useSparkAppStore()
|
|
84
|
+
|
|
108
85
|
const appsWithCurrent = computed(() => {
|
|
109
|
-
|
|
86
|
+
const currentAppName = appStore.state.app
|
|
87
|
+
return appSelectorStore.state.apps.map(app => ({
|
|
110
88
|
...app,
|
|
111
|
-
current: app.name ===
|
|
89
|
+
current: currentAppName && app.name.toLowerCase() === currentAppName.toLowerCase()
|
|
112
90
|
}))
|
|
113
91
|
})
|
|
114
92
|
|
|
115
|
-
const
|
|
116
|
-
window.open(app.href, '_blank')
|
|
117
|
-
|
|
93
|
+
const handleAppClick = (app, event) => {
|
|
118
94
|
emit('select', app)
|
|
119
95
|
}
|
|
120
96
|
</script>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
class="grid w-[40px] h-[40px] place-items-center rounded-md bg-primary-600 text-white text-[13px] cursor-pointer"
|
|
11
11
|
@click.prevent="mainNavStore.goto(appStore.state.homeRoute)"
|
|
12
12
|
>
|
|
13
|
-
<font-awesome-icon :icon="Icons[
|
|
13
|
+
<font-awesome-icon :icon="Icons[appIcon]" class="size-5" />
|
|
14
14
|
</a>
|
|
15
15
|
<a
|
|
16
16
|
@click.prevent="mainNavStore.goto(appStore.state.homeRoute)"
|
|
@@ -165,11 +165,12 @@
|
|
|
165
165
|
</template>
|
|
166
166
|
|
|
167
167
|
<script setup>
|
|
168
|
-
import { computed } from 'vue'
|
|
168
|
+
import { computed, h, useSlots } from 'vue'
|
|
169
169
|
import {
|
|
170
170
|
SparkOverlay,
|
|
171
171
|
SparkBrandSelector,
|
|
172
172
|
useSparkBrandFilterStore,
|
|
173
|
+
useSparkAppSelectorStore,
|
|
173
174
|
SparkAppSelector,
|
|
174
175
|
sparkOverlayService,
|
|
175
176
|
SparkModalContainer,
|
|
@@ -185,14 +186,38 @@ const props = defineProps({
|
|
|
185
186
|
type: Object,
|
|
186
187
|
required: true,
|
|
187
188
|
},
|
|
189
|
+
appSelectorSlots: {
|
|
190
|
+
type: Object,
|
|
191
|
+
default: () => ({}),
|
|
192
|
+
},
|
|
188
193
|
})
|
|
189
194
|
|
|
195
|
+
const slots = useSlots()
|
|
196
|
+
|
|
190
197
|
const sparkBrandFilterStore = useSparkBrandFilterStore()
|
|
198
|
+
const sparkAppSelectorStore = useSparkAppSelectorStore()
|
|
199
|
+
|
|
200
|
+
const appIcon = computed(() => {
|
|
201
|
+
return sparkAppSelectorStore.getAppIcon(props.appStore.state.app)
|
|
202
|
+
})
|
|
191
203
|
|
|
192
204
|
const toggleAppSelector = () => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
205
|
+
// Create component wrappers for slots if they exist
|
|
206
|
+
const slotProps = {}
|
|
207
|
+
|
|
208
|
+
if (slots['app-selector-bottom']) {
|
|
209
|
+
slotProps.bottomSlot = () => h('div', {}, slots['app-selector-bottom']())
|
|
210
|
+
} else if (props.appSelectorSlots.bottomSlot) {
|
|
211
|
+
slotProps.bottomSlot = props.appSelectorSlots.bottomSlot
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (slots['app-selector-footer']) {
|
|
215
|
+
slotProps.footerSlot = () => h('div', {}, slots['app-selector-footer']())
|
|
216
|
+
} else if (props.appSelectorSlots.footerSlot) {
|
|
217
|
+
slotProps.footerSlot = props.appSelectorSlots.footerSlot
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
sparkOverlayService.showRight(SparkAppSelector, slotProps)
|
|
196
221
|
}
|
|
197
222
|
|
|
198
223
|
const toggleBrandSelector = () => {
|
package/src/plugins/router.js
CHANGED
|
@@ -14,7 +14,7 @@ export function createAuthRoutes(options = {}) {
|
|
|
14
14
|
loginPath = '/login',
|
|
15
15
|
logoutPath = '/logout',
|
|
16
16
|
forgotPasswordPath = '/forgot-password',
|
|
17
|
-
resetPasswordPath = '/reset
|
|
17
|
+
resetPasswordPath = '/password/reset',
|
|
18
18
|
logo = '',
|
|
19
19
|
defaultRedirect = '/dashboard',
|
|
20
20
|
} = options
|
|
@@ -43,7 +43,7 @@ export function createAuthRoutes(options = {}) {
|
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
path: resetPasswordPath,
|
|
46
|
-
name: 'reset
|
|
46
|
+
name: 'password-reset',
|
|
47
47
|
component: SparkResetPasswordView,
|
|
48
48
|
props: { logo, loginRoute: loginPath },
|
|
49
49
|
meta: { auth: false },
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { computed, reactive } from 'vue'
|
|
3
|
+
|
|
4
|
+
const DEFAULT_APPS = [
|
|
5
|
+
{
|
|
6
|
+
name: '3CX',
|
|
7
|
+
description: 'VOIP Phone',
|
|
8
|
+
href: 'https://tmrg.3cx.com.au:5001/webclient',
|
|
9
|
+
icon: 'farLaptopMobile',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: 'Buzz',
|
|
13
|
+
description: 'Communication on the go',
|
|
14
|
+
href: 'https://buzz-next.letsbolt.io',
|
|
15
|
+
icon: 'farSatelliteDish',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Dash',
|
|
19
|
+
description: 'Financial powerhouse',
|
|
20
|
+
href: 'https://dash-next.letsbolt.io',
|
|
21
|
+
icon: 'farScaleBalanced',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'MAPit',
|
|
25
|
+
description: 'Geolocation everything',
|
|
26
|
+
href: 'https://mapit-next.letsbolt.io',
|
|
27
|
+
icon: 'farStreetView',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'ProspectR',
|
|
31
|
+
description: 'Leads management',
|
|
32
|
+
href: 'https://prospectr-next.letsbolt.io',
|
|
33
|
+
icon: 'farFaceSmileRelaxed',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'ReVuze',
|
|
37
|
+
description: 'Get Customer feedback',
|
|
38
|
+
href: 'https://revuze-next.letsbolt.io',
|
|
39
|
+
icon: 'farComments',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'Tabula',
|
|
43
|
+
description: 'Admin interface',
|
|
44
|
+
href: 'https://tabula.letsbolt.io',
|
|
45
|
+
icon: 'farCompass',
|
|
46
|
+
},
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
export const useSparkAppSelectorStore = defineStore('sparkAppSelector', () => {
|
|
50
|
+
const state = reactive({
|
|
51
|
+
apps: [...DEFAULT_APPS],
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Initialize app selector store with either partial href config or full app override
|
|
56
|
+
* @param {Object} config - Configuration object
|
|
57
|
+
* @param {Object} config.appHrefs - Object mapping app names to hrefs (case-insensitive)
|
|
58
|
+
* Example: { 'Buzz': 'https://buzz.staging.example.com' }
|
|
59
|
+
* @param {Array} config.apps - Complete array of app objects to override defaults
|
|
60
|
+
* Example: [{ name: 'Buzz', description: '...', icon: '...', href: '...' }]
|
|
61
|
+
*/
|
|
62
|
+
const initialize = (config = {}) => {
|
|
63
|
+
// Full override: if apps array is provided, use it entirely
|
|
64
|
+
if (config.apps && Array.isArray(config.apps)) {
|
|
65
|
+
const validApps = config.apps.filter((app) => {
|
|
66
|
+
const isValid = app.name && app.description && app.icon && app.href
|
|
67
|
+
if (!isValid) {
|
|
68
|
+
console.warn('useSparkAppSelectorStore: Invalid app object', app)
|
|
69
|
+
}
|
|
70
|
+
return isValid
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
state.apps = validApps.map((app) => ({
|
|
74
|
+
name: app.name,
|
|
75
|
+
description: app.description,
|
|
76
|
+
icon: app.icon,
|
|
77
|
+
href: app.href,
|
|
78
|
+
}))
|
|
79
|
+
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Partial config: merge appHrefs into defaults
|
|
84
|
+
if (config.appHrefs && typeof config.appHrefs === 'object') {
|
|
85
|
+
// Create a case-insensitive lookup map
|
|
86
|
+
const hrefMap = new Map()
|
|
87
|
+
Object.keys(config.appHrefs).forEach((appName) => {
|
|
88
|
+
hrefMap.set(appName.toLowerCase(), config.appHrefs[appName])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// Update default apps with provided hrefs
|
|
92
|
+
state.apps = DEFAULT_APPS.map((app) => {
|
|
93
|
+
const customHref = hrefMap.get(app.name.toLowerCase())
|
|
94
|
+
return {
|
|
95
|
+
...app,
|
|
96
|
+
href: customHref || app.href,
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// No config provided: use defaults
|
|
104
|
+
state.apps = [...DEFAULT_APPS]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get all available apps
|
|
109
|
+
*/
|
|
110
|
+
const allApps = computed(() => state.apps)
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get app by name (case-insensitive)
|
|
114
|
+
* @param {string} appName - Name of the app to find
|
|
115
|
+
* @returns {Object|null} App object or null if not found
|
|
116
|
+
*/
|
|
117
|
+
const getAppByName = (appName) => {
|
|
118
|
+
if (!appName) return null
|
|
119
|
+
const normalizedName = appName.toLowerCase()
|
|
120
|
+
return state.apps.find((app) => app.name.toLowerCase() === normalizedName) || null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get icon for a specific app (case-insensitive)
|
|
125
|
+
* @param {string} appName - Name of the app
|
|
126
|
+
* @returns {string} Icon name or empty string if not found
|
|
127
|
+
*/
|
|
128
|
+
const getAppIcon = (appName) => {
|
|
129
|
+
const app = getAppByName(appName)
|
|
130
|
+
return app ? app.icon : ''
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
state,
|
|
135
|
+
initialize,
|
|
136
|
+
allApps,
|
|
137
|
+
getAppByName,
|
|
138
|
+
getAppIcon,
|
|
139
|
+
}
|
|
140
|
+
})
|
package/src/stores/app.js
CHANGED
|
@@ -4,7 +4,6 @@ import { reactive } from 'vue'
|
|
|
4
4
|
export const useSparkAppStore = defineStore('sparkApp', () => {
|
|
5
5
|
const state = reactive({
|
|
6
6
|
app: '',
|
|
7
|
-
icon: '',
|
|
8
7
|
homeRoute: 'dashboard',
|
|
9
8
|
showBrandSelector: true,
|
|
10
9
|
showAppSelector: true,
|
|
@@ -12,7 +11,6 @@ export const useSparkAppStore = defineStore('sparkApp', () => {
|
|
|
12
11
|
|
|
13
12
|
const initialize = (config = {}) => {
|
|
14
13
|
state.app = config.app || ''
|
|
15
|
-
state.icon = config.icon || ''
|
|
16
14
|
state.homeRoute = config.homeRoute ?? 'dashboard'
|
|
17
15
|
state.showBrandSelector = config.showBrandSelector ?? true
|
|
18
16
|
state.showAppSelector = config.showAppSelector ?? true
|
package/src/stores/index.js
CHANGED