Skip to content

Commit

Permalink
Merge pull request #4383 from FlowFuse/2721-product-tour
Browse files Browse the repository at this point in the history
Product Tour - Welcome to FlowFuse
  • Loading branch information
joepavitt authored Aug 16, 2024
2 parents dfac073 + bcb4ab9 commit dd070d7
Show file tree
Hide file tree
Showing 11 changed files with 381 additions and 4 deletions.
5 changes: 5 additions & 0 deletions frontend/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { AxiosError } from 'axios'
import { createApp } from 'vue'

import './ui-components/index.scss'
// import '~shepherd.js/dist/css/shepherd.css'

// Product Tours
import VueShepherdPlugin from 'vue-shepherd'

import App from './App.vue'
import Loading from './components/Loading.vue'
Expand All @@ -24,6 +28,7 @@ const app = createApp(App)
.use(ForgeUIComponents)
.use(store)
.use(router)
.use(VueShepherdPlugin)

// Error tracking
setupSentry(app, router)
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/pages/UnverifiedEmail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,18 @@ export default {
resendTimeout: null
}
},
computed: mapState('account', ['user']),
computed: {
...mapState('account', ['user'])
},
methods: {
async submitVerificationToken () {
try {
await userApi.verifyEmailToken(this.token)
clearTimeout(this.resendTimeout)
window.location = '/'
this.$store.dispatch('ux/activateTour', 'welcome')
this.$router.go()
} catch (err) {
console.error(err)
// Verification failed.
this.token = ''
this.error = 'Verification failed. Click resend to receive a new code to try again'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<section v-if="hasNoDevices" class="ff-no-data--boxed">
<section v-if="hasNoDevices" class="ff-no-data--boxed" data-el="application-devices-none">
<label class="delimiter">
<IconDeviceSolid class="ff-icon ff-icon-sm text-teal-700" />
Devices
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/pages/team/Applications/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,15 @@
<script>
import { PlusSmIcon, SearchIcon } from '@heroicons/vue/outline'
import { mapState } from 'vuex'
import teamApi from '../../../api/team.js'
import EmptyState from '../../../components/EmptyState.vue'
import permissionsMixin from '../../../mixins/Permissions.js'
import Alerts from '../../../services/alerts.js'
import Tours from '../../../tours/Tours.js'
import TourWelcome from '../../../tours/tour-welcome.json'
import ApplicationListItem from './components/Application.vue'
Expand All @@ -127,6 +132,7 @@ export default {
}
},
computed: {
...mapState('ux', ['tours']),
applicationsList () {
return Array.from(this.applications.values()).map(app => {
return {
Expand Down Expand Up @@ -206,6 +212,11 @@ export default {
Alerts.emit('Thanks for signing up to FlowFuse!', 'confirmation')
})
}
// first time arriving here
if (this.tours.welcome) {
const tour = Tours.create('welcome', TourWelcome)
tour.start()
}
},
methods: {
async fetchData (withLoading = true) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
:to="`/application/${application.id}/instances`"
:icon="IconNodeRedSolid"
:class="{'text-gray-400': application.instanceCount === 0}"
data-nav="application-instances"
iconColor="text-red-700"
>
{{ application.instanceCount }}
Expand All @@ -15,6 +16,7 @@
:to="`/application/${application.id}/devices`"
:icon="IconDeviceSolid"
:class="{'text-gray-400': application.deviceCount === 0}"
data-nav="application-devices"
>
{{ application.deviceCount }}
</IconLink>
Expand All @@ -24,6 +26,7 @@
:to="`/application/${application.id}/device-groups`"
:icon="DeviceGroupSolidIcon"
:class="{'text-gray-400': application.deviceGroupCount === 0}"
data-nav="application-device-groups"
iconColor="text-teal-800"
>
{{ application.deviceGroupCount }}
Expand All @@ -34,6 +37,7 @@
:to="`/application/${application.id}/snapshots`"
:icon="IconSnapshotSolid"
:class="{'text-gray-400': application.snapshotCount === 0}"
data-nav="application-snapshots"
>
{{ application.snapshotCount }}
</IconLink>
Expand All @@ -43,6 +47,7 @@
:to="`/application/${application.id}/pipelines`"
:icon="IconPipelineSolid"
:class="{'text-gray-400': application.pipelineCount === 0}"
data-nav="application-pipelines"
>
{{ application.pipelineCount || 0 }}
</IconLink>
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/store/ux.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ const state = () => ({
rightDrawer: {
state: false,
component: null
},
tours: {
welcome: false
}
})

Expand All @@ -19,6 +22,12 @@ const mutations = {
closeRightDrawer (state) {
state.rightDrawer.state = false
state.rightDrawer.component = null
},
activateTour (state, tour) {
state.tours[tour] = true
},
deactivateTour (state, tour) {
state.tours[tour] = false
}
}

Expand All @@ -28,6 +37,12 @@ const actions = {
},
closeRightDrawer ({ commit }) {
commit('closeRightDrawer')
},
activateTour ({ commit }, tour) {
commit('activateTour', tour)
},
deactivateTour ({ commit }, tour) {
commit('deactivateTour', tour)
}
}

Expand All @@ -36,5 +51,12 @@ export default {
state,
getters,
mutations,
actions
actions,
meta: {
persistence: {
tours: {
storage: 'localStorage'
}
}
}
}
115 changes: 115 additions & 0 deletions frontend/src/tours/Tours.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// eslint-disable-next-line n/no-extraneous-import
import { offset } from '@floating-ui/dom'
import { useShepherd } from 'vue-shepherd'

import { useStore } from 'vuex'

import Product from '../services/product.js'

// eslint-disable-next-line n/no-extraneous-import
import 'shepherd.js/dist/css/shepherd.css'
import './tour-theme.scss'

function create (id, tourJson) {
const store = useStore()
Product.capture('ff-tour-start', {
tour_id: id
})
const tour = useShepherd({
useModalOverlay: true,
defaultStepOptions: {
arrow: true,
classes: 'shepherd-theme-ff',
scrollTo: false,
cancelIcon: {
enabled: true
},
floatingUIOptions: {
middleware: [offset({ mainAxis: 12, crossAxis: 0 })]
}
}
})

function onCancel () {
const index = tour.steps.indexOf(tour.currentStep)
store.dispatch('ux/deactivateTour', id)
Product.capture('ff-tour-cancel', {
tour_id: id,
tour_step: index
})
}

function onComplete () {
store.dispatch('ux/deactivateTour', id)
Product.capture('ff-tour-complete', {
tour_id: id
})
}

function onBack () {
tour.back()
const index = tour.steps.indexOf(tour.currentStep)
Product.capture('ff-tour-step-back', {
tour_id: id,
tour_step: index
})
}

function onNext () {
tour.next()
const index = tour.steps.indexOf(tour.currentStep)
Product.capture('ff-tour-step-forward', {
tour_id: id,
tour_step: index
})
}

tour.on('cancel', onCancel)
tour.on('complete', onComplete)

// loop over steps and add them to the tour
const steps = tourJson.length

tourJson.forEach((step, i) => {
const buttons = []

// which secondary button do we need?
if (i === 0) {
buttons.push({
text: 'Exit',
action: tour.cancel,
secondary: true
})
} else {
buttons.push({
text: 'Back',
action: onBack,
secondary: true
})
}

// which primary button do we need?
if (i !== steps - 1) {
buttons.push({
text: 'Next',
action: onNext
})
} else {
buttons.push({
text: 'Finish',
action: tour.complete
})
}

tour.addStep({
...step,
buttons
})
})

return tour
}

export default {
create
}
51 changes: 51 additions & 0 deletions frontend/src/tours/tour-theme.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.shepherd-theme-ff {
&.shepherd-has-title[data-popper-placement^=bottom]>.shepherd-arrow:before {
background-color: $ff-grey-800;
}
.shepherd-content {
.shepherd-header {
background-color: $ff-grey-800;
padding: 0.5rem 1rem;
line-height: 2rem;
}
.shepherd-title {
color: white;
font-weight: 500;
}
.shepherd-cancel-icon {
color: $ff-white;
}
.shepherd-text {
padding: 1rem;
p {
margin-bottom: 0.75rem;
}
}
}
.shepherd-footer {
display: flex;
gap: 0;
padding: 0;
border-top: 1px solid $ff-grey-300;
.shepherd-button {
flex-grow: 1;
border-radius: 0;
background-color: white;
font-weight: 500;
padding: 0.5rem 1rem;
line-height: 2rem;
font-weight: bold;
margin: 0;
}
.shepherd-button:last-child {
background-color: $ff-blue-900;
&:hover {
background-color: $ff-indigo-800;
}
}
}
}

.shepherd-modal-overlay-container.shepherd-modal-is-visible path {
transition: 0.3s all;
}
Loading

0 comments on commit dd070d7

Please sign in to comment.