Skip to content

Commit df7272d

Browse files
committed
added store state for tour
1 parent b670447 commit df7272d

File tree

5 files changed

+98
-36
lines changed

5 files changed

+98
-36
lines changed

src/App.vue

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<template>
22
<div id="app" :class="{'value-masked': amountsHidden}">
33
<!-- (?) This could be moved to Groundfloor.vue -->
4-
<transition v-if="!!$route.params.tourName" name="delay">
5-
<Tour :tourName="$route.params.tourName" />
4+
<transition v-if="showTour" name="delay">
5+
<Tour/>
66
</transition>
77

88
<main :class="routeClass" ref="$main">
@@ -77,7 +77,13 @@ export default defineComponent({
7777
}
7878
});
7979
80-
const { accountInfos } = useAccountStore();
80+
const { accountInfos, state: accountState, removeTour } = useAccountStore();
81+
if (!['root', 'transactions'].includes(context.root.$route.name as string)
82+
&& accountState.tour?.name === 'onboarding') {
83+
removeTour();
84+
}
85+
const showTour = computed(() => !!accountState.tour);
86+
8187
// Convert result of computation to boolean, to not trigger rerender when number of accounts changes above 0.
8288
const hasAccounts = computed(() => Boolean(Object.keys(accountInfos.value).length));
8389
@@ -170,6 +176,7 @@ export default defineComponent({
170176
});
171177
172178
return {
179+
showTour,
173180
routeClass,
174181
hasAccounts,
175182
amountsHidden,

src/components/Tour.vue

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<p
2525
v-for="(content, i) in tour.steps[tour.currentStep].content"
2626
:key="i"
27-
v-html="content"
27+
v-html="$t(content)"
2828
></p>
2929
<!-- TODO REMOVE ME -->
3030
<div class="remove_me" v-if="currentStep === 1" @click="simulate()">
@@ -47,7 +47,7 @@
4747
</v-tour>
4848
<transition name="fade">
4949
<div class="tour-control-bar">
50-
<button disabled>
50+
<button @click="endTour()">
5151
{{ $t("End Tour") }}
5252
</button>
5353
<span class="progress">
@@ -82,6 +82,7 @@
8282
</template>
8383

8484
<script lang="ts">
85+
import { useAccountStore } from '@/stores/Account';
8586
import { useNetworkStore } from '@/stores/Network';
8687
import { useTransactionsStore } from '@/stores/Transactions';
8788
import { CircleSpinner } from '@nimiq/vue-components';
@@ -91,10 +92,11 @@ import {
9192
onMounted,
9293
Ref,
9394
ref,
95+
watch,
9496
} from '@vue/composition-api';
9597
import Vue from 'vue';
9698
import VueTour from 'vue-tour';
97-
import { TourName, TourStep, TourStepIndex, TourSteps, useFakeTx, useTour } from '../composables/useTour';
99+
import { TourStep, TourStepIndex, TourSteps, useFakeTx, useTour } from '../composables/useTour';
98100
import { useWindowSize } from '../composables/useWindowSize';
99101
import CaretRightIcon from './icons/CaretRightIcon.vue';
100102
@@ -104,13 +106,6 @@ require('vue-tour/dist/vue-tour.css');
104106
105107
export default defineComponent({
106108
name: 'tour',
107-
props: {
108-
tourName: {
109-
type: String,
110-
required: true,
111-
validator: (tour: TourName) => (['onboarding', 'network'] as TourName[]).indexOf(tour) !== -1,
112-
},
113-
},
114109
setup(props, context) {
115110
// TODO Use isMobile
116111
const { width } = useWindowSize();
@@ -120,8 +115,10 @@ export default defineComponent({
120115
() => $network.consensus !== 'established',
121116
);
122117
118+
const { state: tourStore, removeTour } = useAccountStore();
119+
123120
let tour: VueTour.Tour | null = null;
124-
const steps: TourSteps<any> = useTour(props.tourName as TourName, context) || {};
121+
const steps: TourSteps<any> = useTour(tourStore.tour, context) || {};
125122
126123
// Initial state
127124
const loading = ref(true);
@@ -142,17 +139,36 @@ export default defineComponent({
142139
nSteps.value = Object.keys(steps).length;
143140
disableNextStep.value = currentStep.value >= nSteps.value - 1
144141
|| !!steps[currentStep.value].ui.disabledNextStep;
142+
145143
_addAttributes(steps[currentStep.value].ui, currentStep.value);
146144
// eslint-disable-next-line no-unused-expressions
147145
steps[currentStep.value].lifecycle?.onMountedStep?.(goToNextStep);
148146
147+
if (context.root.$route.path !== steps[currentStep.value].path) {
148+
context.root.$router.push(steps[currentStep.value].path);
149+
}
150+
149151
await sleep(500);
150152
151153
tour = context.root.$tours['nimiq-tour'];
152154
tour!.start(`${currentStep.value}`);
153155
loading.value = false;
154156
}
155157
158+
// Dont allow user to interact with the page while it is loading
159+
// But allow to end it
160+
watch([loading, disconnected], () => {
161+
const app = document.querySelector('#app main') as HTMLDivElement;
162+
163+
if (loading.value || disconnected.value) {
164+
// eslint-disable-next-line no-unused-expressions
165+
app?.setAttribute('data-non-interactable', '');
166+
} else {
167+
// eslint-disable-next-line no-unused-expressions
168+
app?.removeAttribute('data-non-interactable');
169+
}
170+
});
171+
156172
function goToPrevStep() {
157173
if (currentStep.value <= 0) return;
158174
_moveToFutureStep(currentStep.value, currentStep.value - 1);
@@ -171,8 +187,8 @@ export default defineComponent({
171187
) {
172188
const goingForward = futureStepIndex > currentStepIndex;
173189
174-
const { page: currentPage, lifecycle: currentLifecycle } = steps[currentStepIndex];
175-
const { page: futurePage, ui: futureUI, lifecycle: futureLifecycle } = steps[futureStepIndex];
190+
const { path: currentPage, lifecycle: currentLifecycle } = steps[currentStepIndex];
191+
const { path: futurePage, ui: futureUI, lifecycle: futureLifecycle } = steps[futureStepIndex];
176192
177193
loading.value = true;
178194
tour!.stop();
@@ -183,10 +199,14 @@ export default defineComponent({
183199
await currentLifecycle.prepareDOMPrevPage();
184200
} else if (goingForward && currentLifecycle && currentLifecycle.prepareDOMNextPage) {
185201
await currentLifecycle.prepareDOMNextPage();
186-
} else if (futurePage !== currentPage && currentPage.startsWith(context.root.$route.path)) {
187-
// Default prepare DOM
188-
context.root.$router.push(futurePage);
189-
await context.root.$nextTick();
202+
} else if (futurePage !== currentPage) {
203+
try {
204+
// Default prepare DOM
205+
context.root.$router.push(futurePage);
206+
await context.root.$nextTick();
207+
} catch {
208+
// Ignore error
209+
}
190210
}
191211
192212
_addAttributes(futureUI, futureStepIndex);
@@ -243,6 +263,16 @@ export default defineComponent({
243263
});
244264
}
245265
266+
function endTour() {
267+
_removeAttributes(currentStep.value);
268+
269+
// If user finalizes tour while it is loading, allow then interaction
270+
const app = document.querySelector('#app main') as HTMLDivElement;
271+
app.removeAttribute('data-non-interactable');
272+
273+
removeTour();
274+
}
275+
246276
// TODO REMOVE ME - Simulate tx
247277
function simulate() {
248278
const { addTransactions } = useTransactionsStore();
@@ -263,6 +293,7 @@ export default defineComponent({
263293
// actions
264294
goToPrevStep,
265295
goToNextStep,
296+
endTour,
266297
267298
// TODO REMOVE ME
268299
simulate,
@@ -359,6 +390,7 @@ export default defineComponent({
359390
border: none;
360391
outline: var(--nimiq-);
361392
border-radius: 9999px;
393+
font-weight: 700;
362394
363395
&:disabled {
364396
opacity: 0.5;

src/components/layouts/Settings.vue

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,9 @@
9595
</p>
9696
</div>
9797

98-
<router-link :to="{ name: 'root', params: { tourName: 'onboarding' } }">
99-
<button class="nq-button-pill light-blue">{{ $t('Start Tour') }}</button>
100-
</router-link>
98+
<button class="nq-button-pill light-blue" @click="goToOnboardingTour()">
99+
{{ $t('Start Tour') }}
100+
</button>
101101
</div>
102102

103103
<!-- <div class="setting">
@@ -275,6 +275,7 @@ import { CircleSpinner } from '@nimiq/vue-components';
275275
// @ts-expect-error missing types for this package
276276
import { Portal } from '@linusborg/vue-simple-portal';
277277
278+
import { useAccountStore } from '@/stores/Account';
278279
import MenuIcon from '../icons/MenuIcon.vue';
279280
import CrossCloseButton from '../CrossCloseButton.vue';
280281
import CountryFlag from '../CountryFlag.vue';
@@ -372,6 +373,12 @@ export default defineComponent({
372373
reader.readAsText(file);
373374
}
374375
376+
function goToOnboardingTour() {
377+
const { setTour } = useAccountStore();
378+
setTour('onboarding');
379+
context.root.$router.push('/');
380+
}
381+
375382
async function onTrialPassword(el: HTMLInputElement) {
376383
let hash: string;
377384
try {
@@ -434,6 +441,7 @@ export default defineComponent({
434441
...settings,
435442
$fileInput,
436443
loadFile,
444+
goToOnboardingTour,
437445
showVestingSetting,
438446
onTrialPassword,
439447
applyWalletUpdate,

src/composables/useTour.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { SetupContext } from '@vue/composition-api';
33
import { useAddressStore } from '../stores/Address';
44

55
export type TourName = 'onboarding' | 'network'
6-
type OnboardingTourPages = '/' | '/transactions' | '/?sidebar=true'
76

87
enum MobileOnboardingTourStep {
98
FIRST_ADDRESS,
@@ -17,7 +16,6 @@ enum MobileOnboardingTourStep {
1716
ONBOARDING_COMPLETED
1817
}
1918

20-
type NetworkTourPages = '/network'
2119
enum NetworkTourStep {
2220
TODO,
2321
}
@@ -32,7 +30,7 @@ type AlignedPlacement = `${BasePlacement}-${Alignment}`;
3230
export type Placement = BasePlacement | AlignedPlacement;
3331

3432
export interface TourStep {
35-
page: OnboardingTourPages | NetworkTourPages;
33+
path: '/' | '/transactions' | '/?sidebar=true' | '/network';
3634

3735
// data for the steps of v-tour
3836
tooltip: {
@@ -115,7 +113,7 @@ function getOnboardingTourSteps({ root }: SetupContext): TourSteps<MobileOnboard
115113

116114
const steps: TourSteps<MobileOnboardingTourStep> = {
117115
[MobileOnboardingTourStep.FIRST_ADDRESS]: {
118-
page: '/',
116+
path: '/',
119117
tooltip: {
120118
target: '.address-list > .address-button .identicon img',
121119
content: [
@@ -127,6 +125,13 @@ function getOnboardingTourSteps({ root }: SetupContext): TourSteps<MobileOnboard
127125
},
128126
},
129127
lifecycle: {
128+
prepareDOMNextPage: async () => {
129+
if (root.$route.path === '/') {
130+
const addressButton = document
131+
.querySelector('.address-list > .address-button') as HTMLButtonElement;
132+
addressButton.click();
133+
}
134+
},
130135
onMountedStep: (cbu: () => void) => {
131136
const addressButton = document.querySelector('.address-list > .address-button');
132137

@@ -146,7 +151,7 @@ function getOnboardingTourSteps({ root }: SetupContext): TourSteps<MobileOnboard
146151
},
147152
},
148153
[MobileOnboardingTourStep.TRANSACTIONS_LIST]: {
149-
page: '/transactions',
154+
path: '/transactions',
150155
tooltip: {
151156
target: '.transaction-list > .empty-state h2',
152157
content: [
@@ -169,7 +174,7 @@ function getOnboardingTourSteps({ root }: SetupContext): TourSteps<MobileOnboard
169174
},
170175
},
171176
[MobileOnboardingTourStep.FIRST_TRANSACTION]: {
172-
page: '/transactions',
177+
path: '/transactions',
173178
tooltip: {
174179
target: '.transaction-list .list-element > .transaction > .identicon',
175180
content: [
@@ -198,7 +203,7 @@ function getOnboardingTourSteps({ root }: SetupContext): TourSteps<MobileOnboard
198203
},
199204
},
200205
[MobileOnboardingTourStep.BITCOIN_ADDRESS]: {
201-
page: '/',
206+
path: '/',
202207
tooltip: {
203208
target: '.account-overview .bitcoin-account > .bitcoin-account-item > svg',
204209
content: [
@@ -222,7 +227,7 @@ function getOnboardingTourSteps({ root }: SetupContext): TourSteps<MobileOnboard
222227
},
223228
},
224229
[MobileOnboardingTourStep.WALLET_BALANCE]: {
225-
page: '/',
230+
path: '/',
226231
tooltip: {
227232
target: '.account-overview .account-balance-container .amount',
228233
content: [
@@ -247,7 +252,7 @@ function getOnboardingTourSteps({ root }: SetupContext): TourSteps<MobileOnboard
247252
},
248253
},
249254
[MobileOnboardingTourStep.RECOVERY_WORDS_ALERT]: {
250-
page: '/',
255+
path: '/',
251256
tooltip: {
252257
target: '.account-overview .backup-warning button',
253258
content: [
@@ -270,7 +275,7 @@ function getOnboardingTourSteps({ root }: SetupContext): TourSteps<MobileOnboard
270275
},
271276
},
272277
[MobileOnboardingTourStep.MENU_ICON]: {
273-
page: '/',
278+
path: '/',
274279
tooltip: {
275280
target: '.account-overview .mobile-menu-bar > button.reset',
276281
content: [
@@ -305,7 +310,7 @@ function getOnboardingTourSteps({ root }: SetupContext): TourSteps<MobileOnboard
305310
},
306311
},
307312
[MobileOnboardingTourStep.ACCOUNT_OPTIONS]: {
308-
page: '/?sidebar=true',
313+
path: '/?sidebar=true',
309314
tooltip: {
310315
target: '.modal .small-page',
311316
content: [
@@ -334,7 +339,7 @@ function getOnboardingTourSteps({ root }: SetupContext): TourSteps<MobileOnboard
334339
},
335340
},
336341
[MobileOnboardingTourStep.ONBOARDING_COMPLETED]: {
337-
page: '/?sidebar=true',
342+
path: '/?sidebar=true',
338343
tooltip: {
339344
target: '.column-sidebar .network .consensus-icon',
340345
content: [
@@ -378,10 +383,11 @@ function getOnboardingTourSteps({ root }: SetupContext): TourSteps<MobileOnboard
378383
};
379384
return steps;
380385
}
381-
export function useTour(tour: TourName, context: SetupContext)
386+
export function useTour(tour: TourName | null, context: SetupContext)
382387
: TourSteps<MobileOnboardingTourStep> | TourSteps<NetworkTourStep> | undefined {
383388
if (tour === 'onboarding') {
384389
return getOnboardingTourSteps(context);
385390
}
391+
386392
return undefined;
387393
}

0 commit comments

Comments
 (0)