Skip to content

Commit af545ae

Browse files
committed
[ui] Add product tour for first time users
Redirects to the '/product-tour' route the first time the UI is loaded on a device. The tour uses vue-shepherd to display a series of tooltips that describe some of the website's features and can be skipped any time. When the tour is finished or skipped it is saved as viewed on a cookie to avoid loading it again. Signed-off-by: Eva Millán <[email protected]>
1 parent fec030d commit af545ae

File tree

16 files changed

+687
-6
lines changed

16 files changed

+687
-6
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: New user onboarding
3+
category: added
4+
author: Eva Millan <[email protected]>
5+
issue: null
6+
notes: >
7+
Loading the user interface for the fist time
8+
takes users to an optional tour explaining
9+
SortingHat's most relevant features.

ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"vue": "^2.7.14",
2525
"vue-apollo": "^3.1.0",
2626
"vue-router": "^3.4.9",
27+
"vue-shepherd": "0.3.0",
2728
"vuetify": "^2.6.10",
2829
"vuex": "^3.1.3",
2930
"yargs": "^17.0.1"

ui/public/images/affiliate.gif

93 KB
Loading

ui/public/images/lock.gif

24.3 KB
Loading

ui/public/images/merge.gif

74.5 KB
Loading

ui/public/images/workspace.gif

26.3 KB
Loading

ui/src/components/IndividualsTable.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
:items-per-page="itemsPerPage"
115115
:loading="loading"
116116
>
117-
<template v-slot:item="{ item, expand, isExpanded }">
117+
<template v-slot:item="{ item, expand, isExpanded, index }">
118118
<individual-entry
119119
draggable
120120
:name="item.name"
@@ -144,6 +144,7 @@
144144
@lock="handleLock(item.uuid, $event)"
145145
@enroll="confirmEnroll(item, $event)"
146146
@openMatchesModal="openMatchesModal(item.uuid)"
147+
:ref="`indv_entry_${index}`"
147148
/>
148149
</template>
149150
<template v-if="isExpandable" v-slot:expanded-item="{ item }">
@@ -160,7 +161,7 @@
160161
@unmerge="unmerge($event)"
161162
@withdraw="removeAffiliation($event, item.uuid)"
162163
@updateEnrollment="updateEnrollmentDate"
163-
@openEnrollmentModal="confirmEnroll"
164+
@openEnrollmentModal="confirmEnroll(item, $event)"
164165
@openTeamModal="openTeamModal"
165166
/>
166167
</template>

ui/src/components/OrganizationsTable.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
:page.sync="page"
6565
:loading="loading"
6666
>
67-
<template v-slot:item="{ item, expand, isExpanded }">
67+
<template v-slot:item="{ item, expand, isExpanded, index }">
6868
<organization-entry
6969
:name="item.name"
7070
:enrollments="getEnrolledIndividuals(item.enrollments)"
@@ -79,6 +79,7 @@
7979
@delete="confirmDelete(item.name)"
8080
@getEnrollments="$emit('getEnrollments', { enrollment: item.name })"
8181
@addTeam="createTeam(item.name, $event)"
82+
:ref="`org_entry_${index}`"
8283
/>
8384
</template>
8485
<template v-slot:expanded-item="{ item }">

ui/src/main.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Cookies from "js-cookie";
1212
import { ApolloLink } from "apollo-link";
1313
import Logger from "./plugins/logger";
1414
import GetErrorMessage from "./plugins/errors";
15+
import VueShepherd from "vue-shepherd";
1516

1617
const API_URL = process.env.VUE_APP_API_URL || `${process.env.BASE_URL}api/`;
1718

@@ -54,6 +55,7 @@ fetch(API_URL, { credentials: "include" }).then(() => {
5455
Vue.use(VueRouter);
5556
Vue.use(Logger);
5657
Vue.use(GetErrorMessage);
58+
Vue.use(VueShepherd);
5759

5860
const apolloProvider = new VueApollo({
5961
defaultClient: apolloClient,

ui/src/router/index.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const routes = [
66
path: "/",
77
name: "Dashboard",
88
component: () => import("../views/Dashboard"),
9-
meta: { requiresAuth: true, title: "Sorting Hat" },
9+
meta: { requiresAuth: true, showTour: true, title: "Sorting Hat" },
1010
},
1111
{
1212
path: "/individual/:mk",
@@ -50,6 +50,12 @@ const routes = [
5050
component: () => import("../views/Organization"),
5151
meta: { requiresAuth: true, title: "Sorting Hat" },
5252
},
53+
{
54+
path: "/product-tour",
55+
name: "ProductTour",
56+
component: () => import("../views/ProductTour"),
57+
meta: { requiresAuth: true, title: "Sorting Hat" },
58+
},
5359
];
5460

5561
const router = new Router({
@@ -60,11 +66,19 @@ const router = new Router({
6066

6167
router.beforeEach((to, from, next) => {
6268
const isAuthenticated = store.getters.isAuthenticated;
69+
const showTour =
70+
to.matched.some((record) => record.meta.showTour) &&
71+
store.getters.shouldShowTour;
72+
6373
if (to.matched.some((record) => record.meta.requiresAuth)) {
6474
if (!isAuthenticated) {
6575
next({
6676
path: "/login",
6777
});
78+
} else if (showTour) {
79+
next({
80+
path: "/product-tour",
81+
});
6882
} else {
6983
next();
7084
}

0 commit comments

Comments
 (0)