Skip to content

Commit 4882d28

Browse files
authored
Merge branch 'main' into 4381-invites-posthog
2 parents 495a7a6 + 782c37a commit 4882d28

12 files changed

Lines changed: 383 additions & 4 deletions

File tree

docs/install/docker/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ You will also need to update the `etc/flowforge.yml` file to change `base_url` f
158158
Create a folder in the `docker-compose-1.x.0` directory named `certs`, place your .crt and .key files in there, they should be named for the domain without the `*` eg `example.com.crt` & `example.com.key`
159159
You also need to create a copy of the .crt and .key files named `default.crt` & `default.key` in the same folder. This is used for serving unknown hosts.
160160

161+
If the base_url is not in the same domain as the Node-RED instances, you should also create a copy of the crt & key files matching the hostname for the forge app.
162+
161163
In the `docker-compose.yml` file,
162164
- uncomment the line
163165
```yaml

frontend/src/main.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { AxiosError } from 'axios'
44
import { createApp } from 'vue'
55

66
import './ui-components/index.scss'
7+
// import '~shepherd.js/dist/css/shepherd.css'
8+
9+
// Product Tours
10+
import VueShepherdPlugin from 'vue-shepherd'
711

812
import App from './App.vue'
913
import Loading from './components/Loading.vue'
@@ -24,6 +28,7 @@ const app = createApp(App)
2428
.use(ForgeUIComponents)
2529
.use(store)
2630
.use(router)
31+
.use(VueShepherdPlugin)
2732

2833
// Error tracking
2934
setupSentry(app, router)

frontend/src/pages/UnverifiedEmail.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,18 @@ export default {
4444
resendTimeout: null
4545
}
4646
},
47-
computed: mapState('account', ['user']),
47+
computed: {
48+
...mapState('account', ['user'])
49+
},
4850
methods: {
4951
async submitVerificationToken () {
5052
try {
5153
await userApi.verifyEmailToken(this.token)
5254
clearTimeout(this.resendTimeout)
53-
window.location = '/'
55+
this.$store.dispatch('ux/activateTour', 'welcome')
56+
this.$router.go()
5457
} catch (err) {
58+
console.error(err)
5559
// Verification failed.
5660
this.token = ''
5761
this.error = 'Verification failed. Click resend to receive a new code to try again'

frontend/src/pages/team/Applications/components/compact/DevicesWrapper.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<section v-if="hasNoDevices" class="ff-no-data--boxed">
2+
<section v-if="hasNoDevices" class="ff-no-data--boxed" data-el="application-devices-none">
33
<label class="delimiter">
44
<IconDeviceSolid class="ff-icon ff-icon-sm text-teal-700" />
55
Devices

frontend/src/pages/team/Applications/index.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,15 @@
100100
<script>
101101
import { PlusSmIcon, SearchIcon } from '@heroicons/vue/outline'
102102
103+
import { mapState } from 'vuex'
104+
103105
import teamApi from '../../../api/team.js'
104106
import EmptyState from '../../../components/EmptyState.vue'
105107
import permissionsMixin from '../../../mixins/Permissions.js'
106108
import Alerts from '../../../services/alerts.js'
109+
import Tours from '../../../tours/Tours.js'
110+
111+
import TourWelcome from '../../../tours/tour-welcome.json'
107112
108113
import ApplicationListItem from './components/Application.vue'
109114
@@ -127,6 +132,7 @@ export default {
127132
}
128133
},
129134
computed: {
135+
...mapState('ux', ['tours']),
130136
applicationsList () {
131137
return Array.from(this.applications.values()).map(app => {
132138
return {
@@ -206,6 +212,11 @@ export default {
206212
Alerts.emit('Thanks for signing up to FlowFuse!', 'confirmation')
207213
})
208214
}
215+
// first time arriving here
216+
if (this.tours.welcome) {
217+
const tour = Tours.create('welcome', TourWelcome)
218+
tour.start()
219+
}
209220
},
210221
methods: {
211222
async fetchData (withLoading = true) {

frontend/src/pages/team/components/ApplicationSummaryLabel.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
:to="`/application/${application.id}/instances`"
66
:icon="IconNodeRedSolid"
77
:class="{'text-gray-400': application.instanceCount === 0}"
8+
data-nav="application-instances"
89
iconColor="text-red-700"
910
>
1011
{{ application.instanceCount }}
@@ -15,6 +16,7 @@
1516
:to="`/application/${application.id}/devices`"
1617
:icon="IconDeviceSolid"
1718
:class="{'text-gray-400': application.deviceCount === 0}"
19+
data-nav="application-devices"
1820
>
1921
{{ application.deviceCount }}
2022
</IconLink>
@@ -24,6 +26,7 @@
2426
:to="`/application/${application.id}/device-groups`"
2527
:icon="DeviceGroupSolidIcon"
2628
:class="{'text-gray-400': application.deviceGroupCount === 0}"
29+
data-nav="application-device-groups"
2730
iconColor="text-teal-800"
2831
>
2932
{{ application.deviceGroupCount }}
@@ -34,6 +37,7 @@
3437
:to="`/application/${application.id}/snapshots`"
3538
:icon="IconSnapshotSolid"
3639
:class="{'text-gray-400': application.snapshotCount === 0}"
40+
data-nav="application-snapshots"
3741
>
3842
{{ application.snapshotCount }}
3943
</IconLink>
@@ -43,6 +47,7 @@
4347
:to="`/application/${application.id}/pipelines`"
4448
:icon="IconPipelineSolid"
4549
:class="{'text-gray-400': application.pipelineCount === 0}"
50+
data-nav="application-pipelines"
4651
>
4752
{{ application.pipelineCount || 0 }}
4853
</IconLink>

frontend/src/store/ux.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ const state = () => ({
22
rightDrawer: {
33
state: false,
44
component: null
5+
},
6+
tours: {
7+
welcome: false
58
}
69
})
710

@@ -19,6 +22,12 @@ const mutations = {
1922
closeRightDrawer (state) {
2023
state.rightDrawer.state = false
2124
state.rightDrawer.component = null
25+
},
26+
activateTour (state, tour) {
27+
state.tours[tour] = true
28+
},
29+
deactivateTour (state, tour) {
30+
state.tours[tour] = false
2231
}
2332
}
2433

@@ -28,6 +37,12 @@ const actions = {
2837
},
2938
closeRightDrawer ({ commit }) {
3039
commit('closeRightDrawer')
40+
},
41+
activateTour ({ commit }, tour) {
42+
commit('activateTour', tour)
43+
},
44+
deactivateTour ({ commit }, tour) {
45+
commit('deactivateTour', tour)
3146
}
3247
}
3348

@@ -36,5 +51,12 @@ export default {
3651
state,
3752
getters,
3853
mutations,
39-
actions
54+
actions,
55+
meta: {
56+
persistence: {
57+
tours: {
58+
storage: 'localStorage'
59+
}
60+
}
61+
}
4062
}

frontend/src/tours/Tours.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// eslint-disable-next-line n/no-extraneous-import
2+
import { offset } from '@floating-ui/dom'
3+
import { useShepherd } from 'vue-shepherd'
4+
5+
import { useStore } from 'vuex'
6+
7+
import Product from '../services/product.js'
8+
9+
// eslint-disable-next-line n/no-extraneous-import
10+
import 'shepherd.js/dist/css/shepherd.css'
11+
import './tour-theme.scss'
12+
13+
function create (id, tourJson) {
14+
const store = useStore()
15+
Product.capture('ff-tour-start', {
16+
tour_id: id
17+
})
18+
const tour = useShepherd({
19+
useModalOverlay: true,
20+
defaultStepOptions: {
21+
arrow: true,
22+
classes: 'shepherd-theme-ff',
23+
scrollTo: false,
24+
cancelIcon: {
25+
enabled: true
26+
},
27+
floatingUIOptions: {
28+
middleware: [offset({ mainAxis: 12, crossAxis: 0 })]
29+
}
30+
}
31+
})
32+
33+
function onCancel () {
34+
const index = tour.steps.indexOf(tour.currentStep)
35+
store.dispatch('ux/deactivateTour', id)
36+
Product.capture('ff-tour-cancel', {
37+
tour_id: id,
38+
tour_step: index
39+
})
40+
}
41+
42+
function onComplete () {
43+
store.dispatch('ux/deactivateTour', id)
44+
Product.capture('ff-tour-complete', {
45+
tour_id: id
46+
})
47+
}
48+
49+
function onBack () {
50+
tour.back()
51+
const index = tour.steps.indexOf(tour.currentStep)
52+
Product.capture('ff-tour-step-back', {
53+
tour_id: id,
54+
tour_step: index
55+
})
56+
}
57+
58+
function onNext () {
59+
tour.next()
60+
const index = tour.steps.indexOf(tour.currentStep)
61+
Product.capture('ff-tour-step-forward', {
62+
tour_id: id,
63+
tour_step: index
64+
})
65+
}
66+
67+
tour.on('cancel', onCancel)
68+
tour.on('complete', onComplete)
69+
70+
// loop over steps and add them to the tour
71+
const steps = tourJson.length
72+
73+
tourJson.forEach((step, i) => {
74+
const buttons = []
75+
76+
// which secondary button do we need?
77+
if (i === 0) {
78+
buttons.push({
79+
text: 'Exit',
80+
action: tour.cancel,
81+
secondary: true
82+
})
83+
} else {
84+
buttons.push({
85+
text: 'Back',
86+
action: onBack,
87+
secondary: true
88+
})
89+
}
90+
91+
// which primary button do we need?
92+
if (i !== steps - 1) {
93+
buttons.push({
94+
text: 'Next',
95+
action: onNext
96+
})
97+
} else {
98+
buttons.push({
99+
text: 'Finish',
100+
action: tour.complete
101+
})
102+
}
103+
104+
tour.addStep({
105+
...step,
106+
buttons
107+
})
108+
})
109+
110+
return tour
111+
}
112+
113+
export default {
114+
create
115+
}

frontend/src/tours/tour-theme.scss

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
.shepherd-theme-ff {
2+
&.shepherd-has-title[data-popper-placement^=bottom]>.shepherd-arrow:before {
3+
background-color: $ff-grey-800;
4+
}
5+
.shepherd-content {
6+
.shepherd-header {
7+
background-color: $ff-grey-800;
8+
padding: 0.5rem 1rem;
9+
line-height: 2rem;
10+
}
11+
.shepherd-title {
12+
color: white;
13+
font-weight: 500;
14+
}
15+
.shepherd-cancel-icon {
16+
color: $ff-white;
17+
}
18+
.shepherd-text {
19+
padding: 1rem;
20+
p {
21+
margin-bottom: 0.75rem;
22+
}
23+
}
24+
}
25+
.shepherd-footer {
26+
display: flex;
27+
gap: 0;
28+
padding: 0;
29+
border-top: 1px solid $ff-grey-300;
30+
.shepherd-button {
31+
flex-grow: 1;
32+
border-radius: 0;
33+
background-color: white;
34+
font-weight: 500;
35+
padding: 0.5rem 1rem;
36+
line-height: 2rem;
37+
font-weight: bold;
38+
margin: 0;
39+
}
40+
.shepherd-button:last-child {
41+
background-color: $ff-blue-900;
42+
&:hover {
43+
background-color: $ff-indigo-800;
44+
}
45+
}
46+
}
47+
}
48+
49+
.shepherd-modal-overlay-container.shepherd-modal-is-visible path {
50+
transition: 0.3s all;
51+
}

0 commit comments

Comments
 (0)