diff --git a/.tx/config b/.tx/config
index 5026ff175..1d1fe4c52 100644
--- a/.tx/config
+++ b/.tx/config
@@ -7,4 +7,3 @@ minimum_perc = 0
source_file = src/i18n/en.po
source_lang = en
type = PO
-
diff --git a/package.json b/package.json
index 3e3522536..c5164907f 100644
--- a/package.json
+++ b/package.json
@@ -49,6 +49,7 @@
"vue-responsive-components": "^0.2.3",
"vue-router": "^3.1.6",
"vue-simple-markdown": "^1.1.4",
+ "vue-tour": "^2.0.0",
"vue-virtual-scroller": "https://github.com/sisou/vue-virtual-scroller#nimiq/build",
"webpack-i18n-tools": "https://github.com/nimiq/webpack-i18n-tools#master"
},
diff --git a/src/App.vue b/src/App.vue
index e2b0b81cf..a63c5de5b 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,5 +1,9 @@
+
+
+
+
@@ -44,7 +48,19 @@ export default defineComponent({
const { activeMobileColumn } = useActiveMobileColumn();
- const { accountInfos } = useAccountStore();
+ const { accountInfos, state: accountState, setTour } = useAccountStore();
+ const path = context.root.$route.path as string || '';
+ const tourName = accountState.tour?.name || '';
+ if (
+ // if we are in onboarding tour and the path is NOT one of the path in the onboarding tour
+ // or if we are in network tour and the path is NOT one of the path in the network tour
+ // then remove tour state
+ (!['/', '/transactions', '/accounts'].includes(path) && tourName === 'onboarding')
+ || (!['/network'].includes(path) && tourName === 'network')) {
+ setTour(null);
+ }
+ const showTour = computed(() => !!accountState.tour);
+
// Convert result of computation to boolean, to not trigger rerender when number of accounts changes above 0.
const hasAccounts = computed(() => Boolean(Object.keys(accountInfos.value).length));
@@ -53,7 +69,7 @@ export default defineComponent({
// Swiping
const $main = ref(null);
let $mobileTapArea: HTMLDivElement | null = null;
- const { width, isMobile } = useWindowSize();
+ const { width, isSmallScreen } = useWindowSize();
async function updateSwipeRestPosition(
velocityDistance: number,
@@ -122,21 +138,22 @@ export default defineComponent({
excludeSelector: '.scroller, .scroller *',
});
- watch([isMobile, swipingEnabled], ([isMobileNow, newSwiping], [wasMobile, oldSwiping]) => {
+ watch([isSmallScreen, swipingEnabled], ([isSmallScreenNow, newSwiping], [wasSmallScreen, oldSwiping]) => {
if (!$main.value) return;
- if ((isMobileNow && !wasMobile) || (newSwiping === 1 && oldSwiping !== 1)) {
+ if ((isSmallScreenNow && !wasSmallScreen) || (newSwiping === 1 && oldSwiping !== 1)) {
attachSwipe();
- } else if (!isMobileNow || newSwiping !== 1) {
+ } else if (!isSmallScreenNow || newSwiping !== 1) {
detachSwipe();
}
}, { lazy: true });
onMounted(() => {
- if (isMobile.value && swipingEnabled.value === 1) attachSwipe();
+ if (isSmallScreen.value && swipingEnabled.value === 1) attachSwipe();
});
return {
+ showTour,
activeMobileColumn,
hasAccounts,
amountsHidden,
@@ -148,6 +165,9 @@ export default defineComponent({
SwapNotification,
UpdateNotification,
LoadingSpinner,
+
+ // lazy loading
+ Tour: () => import(/* webpackChunkName: "tour" */ './components/Tour.vue'),
},
});
@@ -157,6 +177,7 @@ export default defineComponent({
#app {
@include flex-full-height;
@include ios-flex;
+
overflow: hidden; // To prevent horizontal scrollbars during panel sliding
touch-action: pan-y;
diff --git a/src/assets/languages/de.svg b/src/assets/languages/de.svg
deleted file mode 100644
index 0a48171bf..000000000
--- a/src/assets/languages/de.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/languages/en.svg b/src/assets/languages/en.svg
deleted file mode 100644
index b8ac7687a..000000000
--- a/src/assets/languages/en.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/languages/es.svg b/src/assets/languages/es.svg
deleted file mode 100644
index f94e793c4..000000000
--- a/src/assets/languages/es.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/languages/fr.svg b/src/assets/languages/fr.svg
deleted file mode 100644
index caca31233..000000000
--- a/src/assets/languages/fr.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/languages/nl.svg b/src/assets/languages/nl.svg
deleted file mode 100644
index c8f3a2830..000000000
--- a/src/assets/languages/nl.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/src/assets/languages/ru.svg b/src/assets/languages/ru.svg
deleted file mode 100644
index 79db37166..000000000
--- a/src/assets/languages/ru.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/assets/languages/uk.svg b/src/assets/languages/uk.svg
deleted file mode 100644
index 002d65426..000000000
--- a/src/assets/languages/uk.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/src/assets/languages/zh.svg b/src/assets/languages/zh.svg
deleted file mode 100644
index bd1c32d3c..000000000
--- a/src/assets/languages/zh.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/slides/browser-network-half.png b/src/assets/slides/browser-network-half.png
deleted file mode 100644
index c5adb7935..000000000
Binary files a/src/assets/slides/browser-network-half.png and /dev/null differ
diff --git a/src/components/AccountBalance.vue b/src/components/AccountBalance.vue
index e4dfc9923..1ad5ed1e4 100644
--- a/src/components/AccountBalance.vue
+++ b/src/components/AccountBalance.vue
@@ -63,8 +63,8 @@ export default defineComponent({
const $fiatAmountContainer = ref(null);
const $fiatAmount = ref(null);
- const { isFullDesktop } = useWindowSize();
- const fiatAmountMaxSize = computed(() => isFullDesktop.value ? 7 : 5.5); // rem
+ const { isLargeScreen } = useWindowSize();
+ const fiatAmountMaxSize = computed(() => isLargeScreen.value ? 7 : 5.5); // rem
const fiatAmountFontSize = ref(fiatAmountMaxSize.value);
async function updateFontSize() {
diff --git a/src/components/AccountMenu.vue b/src/components/AccountMenu.vue
index 0bcca8b03..2c7c63315 100644
--- a/src/components/AccountMenu.vue
+++ b/src/components/AccountMenu.vue
@@ -37,6 +37,7 @@ import { useAccountStore, AccountType } from '../stores/Account';
import { useAddressStore } from '../stores/Address';
export default defineComponent({
+ name: 'account-menu',
setup(props, context) {
const { activeAccountInfo } = useAccountStore();
const { state: addressState } = useAddressStore();
diff --git a/src/components/BalanceDistribution.vue b/src/components/BalanceDistribution.vue
index 4eab61aea..af55ae1ac 100644
--- a/src/components/BalanceDistribution.vue
+++ b/src/components/BalanceDistribution.vue
@@ -362,11 +362,9 @@ export default defineComponent({
margin-left: -0.375rem;
margin-right: -0.375rem;
line-height: 1;
+ width: min-content;
- position: absolute;
z-index: 3;
- top: 3rem;
-
transition: all 200ms var(--nimiq-ease);
&.exchange-is-close {
diff --git a/src/components/BtcTransactionList.vue b/src/components/BtcTransactionList.vue
index b6e3bd9e0..1935d5dc6 100644
--- a/src/components/BtcTransactionList.vue
+++ b/src/components/BtcTransactionList.vue
@@ -150,8 +150,8 @@ export default defineComponent({
const scrollerBuffer = 300;
// Height of items in pixel
- const { isMobile } = useWindowSize();
- const itemSize = computed(() => isMobile.value ? 68 : 72); // mobile: 64px + 4px margin between items
+ const { isSmallScreen } = useWindowSize();
+ const itemSize = computed(() => isSmallScreen.value ? 68 : 72); // mobile: 64px + 4px margin between items
// Get all transactions for the active addresses
const txsForActiveAddress = computed(() => Object.values(btcTransactions$.transactions)
diff --git a/src/components/CountrySelector.vue b/src/components/CountrySelector.vue
index 47b717acd..91b07a185 100644
--- a/src/components/CountrySelector.vue
+++ b/src/components/CountrySelector.vue
@@ -42,7 +42,7 @@
+
+
diff --git a/src/components/Tour.vue b/src/components/Tour.vue
new file mode 100644
index 000000000..b4dbd2a88
--- /dev/null
+++ b/src/components/Tour.vue
@@ -0,0 +1,1038 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ‘{{$t('Back to addresses')}}’
+
+
+
+
+
{{ $t(content) }}
+
+ -
+ -
+ {{ $t(item) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ currentStep + 1 }} / {{ nSteps }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/TourLargeScreenManager.vue b/src/components/TourLargeScreenManager.vue
new file mode 100644
index 000000000..3a687b1a8
--- /dev/null
+++ b/src/components/TourLargeScreenManager.vue
@@ -0,0 +1,168 @@
+
+
+
+
+ {{$t('Use the tooltips to navigate your tour.')}}
+
+
+
+ {{ currentStep + 1 }} / {{ nSteps }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/TransactionList.vue b/src/components/TransactionList.vue
index 44603bc22..3dec4eaae 100644
--- a/src/components/TransactionList.vue
+++ b/src/components/TransactionList.vue
@@ -61,8 +61,10 @@
>
{{ $t('Cashlink') }}
-
- {{ $t('Buy NIM') }}
+
+
@@ -143,6 +145,7 @@ function getLocaleMonthStringFromDate(
// }
export default defineComponent({
+ name: 'transactions-list',
props: {
searchString: {
type: String,
@@ -163,8 +166,8 @@ export default defineComponent({
const scrollerBuffer = 300;
// Height of items in pixel
- const { isMobile } = useWindowSize();
- const itemSize = computed(() => isMobile.value ? 68 : 72); // mobile: 64px + 4px margin between items
+ const { isSmallScreen } = useWindowSize();
+ const itemSize = computed(() => isSmallScreen.value ? 68 : 72); // mobile: 64px + 4px margin between items
// Get all transactions for the active address
const txsForActiveAddress = computed(() => Object.values(transactions$.transactions)
@@ -607,11 +610,11 @@ export default defineComponent({
color: var(--text-60);
font-weight: 600;
margin-bottom: 1rem;
+ }
- a {
- color: inherit;
- text-decoration: underline;
- }
+ a {
+ color: inherit;
+ text-decoration: none;
}
}
diff --git a/src/components/UpdateNotification.vue b/src/components/UpdateNotification.vue
index 3fc73bb21..5043ad622 100644
--- a/src/components/UpdateNotification.vue
+++ b/src/components/UpdateNotification.vue
@@ -18,6 +18,7 @@
-
-
diff --git a/src/components/icons/NimiqLogoOutlineWithStars.vue b/src/components/icons/NimiqLogoOutlineWithStars.vue
new file mode 100644
index 000000000..ae6fa4af9
--- /dev/null
+++ b/src/components/icons/NimiqLogoOutlineWithStars.vue
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/components/icons/PartyConfettiIcon.vue b/src/components/icons/PartyConfettiIcon.vue
new file mode 100644
index 000000000..64662334b
--- /dev/null
+++ b/src/components/icons/PartyConfettiIcon.vue
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/components/icons/SpeechBubble.vue b/src/components/icons/SpeechBubble.vue
new file mode 100644
index 000000000..0387070fb
--- /dev/null
+++ b/src/components/icons/SpeechBubble.vue
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/components/icons/TourPreviousLeftArrowIcon.vue b/src/components/icons/TourPreviousLeftArrowIcon.vue
new file mode 100644
index 000000000..d94119f99
--- /dev/null
+++ b/src/components/icons/TourPreviousLeftArrowIcon.vue
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/components/layouts/AccountOverview.vue b/src/components/layouts/AccountOverview.vue
index a9a75bcb8..3a742402b 100644
--- a/src/components/layouts/AccountOverview.vue
+++ b/src/components/layouts/AccountOverview.vue
@@ -155,12 +155,12 @@ export default defineComponent({
const canHaveMultipleAddresses = computed(() => (activeAccountInfo.value || false)
&& activeAccountInfo.value.type !== AccountType.LEGACY);
- const { isMobile, isTablet } = useWindowSize();
+ const { isSmallScreen, isLargeScreen } = useWindowSize();
function onAddressSelected() {
setActiveCurrency(CryptoCurrency.NIM);
- if (isMobile.value) {
+ if (isSmallScreen.value) {
context.root.$router.push('/transactions');
}
}
@@ -168,7 +168,7 @@ export default defineComponent({
function selectBitcoin() {
setActiveCurrency(CryptoCurrency.BTC);
- if (isMobile.value) {
+ if (isSmallScreen.value) {
context.root.$router.push('/transactions');
}
}
@@ -176,12 +176,12 @@ export default defineComponent({
const showFullLegacyAccountNotice = computed(() =>
isLegacyAccount.value
&& activeAccountInfo.value!.addresses.length === 1
- && !isTablet.value);
+ && isLargeScreen.value);
const showModalLegacyAccountNotice = ref(false);
function determineIfShowModalLegacyAccountNotice() {
- showModalLegacyAccountNotice.value = isLegacyAccount.value && isTablet.value;
+ showModalLegacyAccountNotice.value = isLegacyAccount.value && !isLargeScreen.value;
}
function determineModalToShow() {
diff --git a/src/components/layouts/AddressOverview.vue b/src/components/layouts/AddressOverview.vue
index f76f00622..c8ba76bd8 100644
--- a/src/components/layouts/AddressOverview.vue
+++ b/src/components/layouts/AddressOverview.vue
@@ -250,11 +250,11 @@ export default defineComponent({
}
});
- const { isMobile, isFullDesktop } = useWindowSize();
+ const { isSmallScreen, isLargeScreen } = useWindowSize();
- const addressMaskedWidth = computed(() => isFullDesktop.value
+ const addressMaskedWidth = computed(() => isLargeScreen.value
? 396
- : !isMobile.value
+ : !isSmallScreen.value
? 372
: 322);
diff --git a/src/components/layouts/Network.vue b/src/components/layouts/Network.vue
index f5752b767..b3f06520a 100644
--- a/src/components/layouts/Network.vue
+++ b/src/components/layouts/Network.vue
@@ -56,8 +56,9 @@ import { useSettingsStore } from '../../stores/Settings';
const LOCALSTORAGE_KEY = 'network-info-dismissed';
export default defineComponent({
- setup() {
- const showNetworkInfo = ref(!window.localStorage.getItem(LOCALSTORAGE_KEY));
+ setup(props, context) {
+ const showNetworkInfo = ref(
+ !window.localStorage.getItem(LOCALSTORAGE_KEY) || !!context.root.$route.params.showNetworkInfo);
function onNetworkInfoClosed() {
window.localStorage.setItem(LOCALSTORAGE_KEY, '1');
@@ -119,6 +120,7 @@ export default defineComponent({
.scroller {
max-width: 100vw;
+ scroll-behavior: smooth;
&.map {
width: 100%;
diff --git a/src/components/layouts/Settings.vue b/src/components/layouts/Settings.vue
index 94454263c..f7fe7dd45 100644
--- a/src/components/layouts/Settings.vue
+++ b/src/components/layouts/Settings.vue
@@ -87,6 +87,20 @@
+
+
+
+
+
+ {{ $t('Go through the product again') }}
+
+
+
+
+
+
-
-
-
-
- {{ $t('Your browser is a Node') }}
-
- {{ $t('Connect directly to the Nimiq blockchain.\nBe independent from any middleman.') }}
-
-
-
-
-
-
-
-
- NIM
-
-
-
-
- BTC
-
-
+
+
+
+
+
+
+
+
+ {{ language }}
+
+ {{ option.name }}
+
+
+
+
+
+
+
+
{{ $t('Discover the Nimiq Wallet!') }}
- {{ $t('All in one easy place.') }}
+ {{ $t('It\'s free, does not collect data and is controlled by no one but you.') }}
-
-
-
-
-
-
-
-
- {{ $t('The Nimiq wallet is more than a web-wallet, it is a network node.') }}
-
-
- {{ $t('Send and receive NIM directly on the blockchain.') }}
-
-
- {{ $t('It is fast, safe and makes you truly independent.') }}
-
-
-
-

-
-
-
-
-
-
-
-
- {{ lang.name }}
-
+
+
+
-
+
+
-
diff --git a/src/composables/useEventListener.ts b/src/composables/useEventListener.ts
new file mode 100644
index 000000000..5fbf33b70
--- /dev/null
+++ b/src/composables/useEventListener.ts
@@ -0,0 +1,11 @@
+import { onBeforeUnmount, onMounted } from '@vue/composition-api';
+
+export function useEventListener(
+ target: EventTarget,
+ event: string,
+ handler: (e: any) => any,
+ options?: AddEventListenerOptions | boolean,
+) {
+ onMounted(() => target.addEventListener(event, handler, options));
+ onBeforeUnmount(() => target.removeEventListener(event, handler));
+}
diff --git a/src/composables/useKeys.ts b/src/composables/useKeys.ts
new file mode 100644
index 000000000..840d3055f
--- /dev/null
+++ b/src/composables/useKeys.ts
@@ -0,0 +1,20 @@
+import { Ref } from '@vue/composition-api';
+import { useEventListener } from './useEventListener';
+
+type validKeys = `Arrow${'Right' | 'Left' | 'Up' | 'Down'}` | 'Enter' | 'Escape'
+
+interface KeyEvent {
+ key: validKeys;
+ handler: () => any;
+ options?: { ignoreIf?: Ref, onlyIf?: Ref };
+}
+
+export function useKeys(keyEvents: KeyEvent[]) {
+ useEventListener(window, 'keydown', (e: KeyboardEvent) => {
+ const keyEvent = keyEvents.find((key) => key.key === e.key);
+ if (!keyEvent || keyEvent.options?.ignoreIf?.value || keyEvent.options?.onlyIf?.value === false) return;
+ e.preventDefault();
+ e.stopPropagation();
+ keyEvent.handler();
+ }, true);
+}
diff --git a/src/composables/useMedia.ts b/src/composables/useMedia.ts
new file mode 100644
index 000000000..40f8de08a
--- /dev/null
+++ b/src/composables/useMedia.ts
@@ -0,0 +1,11 @@
+import { Ref, ref } from '@vue/composition-api';
+import { useEventListener } from './useEventListener';
+
+export function useMedia(query: string): Readonly[> {
+ const mediaQuery = window.matchMedia(query);
+ const matches = ref(mediaQuery.matches);
+ useEventListener(mediaQuery, 'change', (e) => {
+ matches.value = e.matches;
+ });
+ return matches;
+}
diff --git a/src/composables/useOutsideClick.ts b/src/composables/useOutsideClick.ts
new file mode 100644
index 000000000..93a9b90da
--- /dev/null
+++ b/src/composables/useOutsideClick.ts
@@ -0,0 +1,49 @@
+import { useEventListener } from './useEventListener';
+
+// Polyfill
+function microTask(cb: () => void) {
+ if (typeof queueMicrotask === 'function') {
+ queueMicrotask(cb);
+ } else {
+ Promise.resolve()
+ .then(cb)
+ .catch((e) =>
+ setTimeout(() => {
+ throw e;
+ }),
+ );
+ }
+}
+
+export function useOutsideClick(
+ selectors: string[],
+ cb: (event: MouseEvent | PointerEvent, target: HTMLElement) => void,
+) {
+ let called = false;
+ function handle(event: MouseEvent | PointerEvent) {
+ event.preventDefault();
+ event.stopPropagation();
+ event.stopImmediatePropagation();
+
+ if (called) return;
+ called = true;
+ microTask(() => {
+ called = false;
+ });
+
+ const target = event.target as HTMLElement;
+
+ // Ignore if the target doesn't exist in the DOM anymore
+ if (!target.ownerDocument.documentElement.contains(target)) return;
+
+ // Ignore if the target exists in one of the containers
+ for (const container of selectors.map((selector) => document.querySelector(selector))) {
+ if (container?.contains(target)) return;
+ }
+
+ cb(event, target);
+ }
+
+ useEventListener(window, 'pointerdown', handle, true);
+ useEventListener(window, 'mousedown', handle, true);
+}
diff --git a/src/composables/useWindowSize.ts b/src/composables/useWindowSize.ts
index b8f1e9d68..c1a39f17a 100644
--- a/src/composables/useWindowSize.ts
+++ b/src/composables/useWindowSize.ts
@@ -1,53 +1,14 @@
-import { ref, onMounted, onUnmounted, Ref, computed } from '@vue/composition-api';
+import { computed, Ref } from '@vue/composition-api';
+import { useMedia } from './useMedia';
-let numberOfListeners = 0;
-
-// FIXME: In Vue 2, composition-api methods cannot be used before the plugin is activated.
-// When switching to Vue 3, the width and height variables can be directly instantiated
-// as refs.
-let width: Ref | null = null;
-let height: Ref | null = null;
-
-let isMobile: Readonly][> | null = null;
-let isTablet: Readonly][> | null = null;
-let isFullDesktop: Readonly][> | null = null;
-
-function listener() {
- width!.value = window.innerWidth;
- height!.value = window.innerHeight;
-}
+export type ScreenTypes = Pick, 'isSmallScreen' | 'isMediumScreen' | 'isLargeScreen'>
export function useWindowSize() {
- // First-time setup
- if (!width || !height || !isMobile || !isTablet || !isFullDesktop) {
- width = ref(0);
- height = ref(0);
- listener();
- isMobile = computed(() => width!.value <= 700); // Full mobile breakpoint
- isTablet = computed(() => width!.value <= 960); // Tablet breakpoint
- isFullDesktop = computed(() => width!.value > 1160); // Desktop breakpoint
- }
-
- onMounted(() => {
- if (numberOfListeners === 0) {
- window.addEventListener('resize', listener);
- }
- numberOfListeners += 1;
- });
-
- onUnmounted(() => {
- numberOfListeners -= 1;
-
- if (numberOfListeners === 0) {
- window.removeEventListener('resize', listener);
- }
- });
-
return {
- width,
- height,
- isMobile,
- isTablet,
- isFullDesktop,
+ width: computed(() => window.innerWidth) as Readonly][>,
+ height: computed(() => window.innerHeight) as Readonly][>,
+ isSmallScreen: useMedia('(max-width: 700px)'),
+ isMediumScreen: useMedia('(min-width: 700px) and (max-width: 1160px)'),
+ isLargeScreen: useMedia('(min-width: 1160px)'),
};
}
diff --git a/src/i18n/en.po b/src/i18n/en.po
index bdd8a6572..824cd1446 100644
--- a/src/i18n/en.po
+++ b/src/i18n/en.po
@@ -2,6 +2,14 @@ msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
+#: src/lib/tour/network/NetworkTourTexts.ts:18
+msgid "‘Available browsers’ are other user’s browsers, just like yours."
+msgstr ""
+
+#: src/lib/tour/network/NetworkTourTexts.ts:19
+msgid "‘Backbone nodes’ provide a fallback to connect to."
+msgstr ""
+
#: src/components/modals/MigrationWelcomeModal.vue:32
msgid "{accounts} hold, manage and aggregate addresses."
msgstr ""
@@ -115,7 +123,7 @@ msgstr ""
msgid "72 hours after your first buy, your limit will be increased automatically."
msgstr ""
-#: src/components/layouts/Settings.vue:339
+#: src/components/layouts/Settings.vue:356
msgid ""
"A contact with the address \"{address}\", but a different name already exists.\n"
" Do you want to replace it?"
@@ -139,7 +147,7 @@ msgstr ""
msgid "Activate Bitcoin"
msgstr ""
-#: src/components/layouts/Settings.vue:198
+#: src/components/layouts/Settings.vue:212
msgid "Add"
msgstr ""
@@ -155,7 +163,7 @@ msgstr ""
msgid "Add a public message..."
msgstr ""
-#: src/components/AccountMenu.vue:70
+#: src/components/modals/AccountMenuModal.vue:43
msgid "Add account"
msgstr ""
@@ -163,7 +171,7 @@ msgstr ""
msgid "Add address"
msgstr ""
-#: src/components/layouts/Settings.vue:194
+#: src/components/layouts/Settings.vue:208
msgid "Add an existing vesting contract to your wallet."
msgstr ""
@@ -186,7 +194,7 @@ msgstr ""
msgid "Addresses"
msgstr ""
-#: src/components/layouts/Settings.vue:152
+#: src/components/layouts/Settings.vue:166
msgid "Advanced"
msgstr ""
@@ -194,7 +202,7 @@ msgstr ""
msgid "After 72 hours"
msgstr ""
-#: src/components/layouts/Settings.vue:132
+#: src/components/layouts/Settings.vue:146
#: src/components/layouts/Settings.vue:72
msgid "all"
msgstr ""
@@ -203,15 +211,15 @@ msgstr ""
msgid "All countries"
msgstr ""
-#: src/components/modals/WelcomeModal.vue:35
-msgid "All in one easy place."
-msgstr ""
-
#: src/components/layouts/AccountOverview.vue:91
#: src/components/LegacyAccountNotice.vue:37
msgid "All new features are exclusive to new accounts. Upgrade now, it only takes seconds."
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:67
+msgid "All security relevant actions can be found here too."
+msgstr ""
+
#: src/components/layouts/AddressOverview.vue:149
msgid "All swaps are part of your transaction history and feature a small swap icon."
msgstr ""
@@ -232,10 +240,6 @@ msgstr ""
msgid "An update to the Wallet is available."
msgstr ""
-#: src/components/modals/WelcomeModal.vue:27
-msgid "And {BTC}, the gold standard of crypto and a sound store of value."
-msgstr ""
-
#: src/components/modals/TradeModal.vue:30
msgid "Android and iOS App"
msgstr ""
@@ -264,11 +268,11 @@ msgstr ""
msgid "Atomic swaps require two BTC transactions."
msgstr ""
-#: src/components/layouts/Settings.vue:184
+#: src/components/layouts/Settings.vue:198
msgid "Auto"
msgstr ""
-#: src/components/layouts/Settings.vue:177
+#: src/components/layouts/Settings.vue:191
msgid "Automatic mode uses {behavior}."
msgstr ""
@@ -277,6 +281,7 @@ msgid "Awaiting swap secret"
msgstr ""
#: src/components/layouts/Network.vue:15
+#: src/components/Tour.vue:55
msgid "Back to addresses"
msgstr ""
@@ -307,7 +312,7 @@ msgstr ""
#: src/components/BalanceDistribution.vue:77
#: src/components/layouts/AccountOverview.vue:64
#: src/components/layouts/AddressOverview.vue:68
-#: src/components/layouts/Settings.vue:119
+#: src/components/layouts/Settings.vue:133
#: src/components/LegacyAccountNotice.vue:21
#: src/components/modals/BtcTransactionModal.vue:119
#: src/components/modals/BtcTransactionModal.vue:135
@@ -320,7 +325,7 @@ msgstr ""
msgid "Bitcoin addresses are used only once, so there are no contacts. Use labels instead to find transactions in your history easily."
msgstr ""
-#: src/components/layouts/Settings.vue:138
+#: src/components/layouts/Settings.vue:152
msgid "Bitcoin Unit"
msgstr ""
@@ -348,7 +353,7 @@ msgstr ""
msgid "BTC network fee"
msgstr ""
-#: src/components/layouts/Sidebar.vue:30
+#: src/components/layouts/Sidebar.vue:33
msgid "Buy"
msgstr ""
@@ -379,7 +384,7 @@ msgstr ""
msgid "Buy more here in the wallet."
msgstr ""
-#: src/components/TransactionList.vue:65
+#: src/components/TransactionList.vue:66
msgid "Buy NIM"
msgstr ""
@@ -436,7 +441,7 @@ msgstr ""
msgid "Cancelled Swap"
msgstr ""
-#: src/components/layouts/Settings.vue:319
+#: src/components/layouts/Settings.vue:336
msgid "Cannot import contacts, wrong file format."
msgstr ""
@@ -454,7 +459,7 @@ msgstr ""
msgid "Cashlink to {address}"
msgstr ""
-#: src/components/AccountMenu.vue:50
+#: src/components/modals/AccountMenuModal.vue:23
msgid "Change password"
msgstr ""
@@ -462,6 +467,11 @@ msgstr ""
msgid "Change your language setting."
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:43
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:48
+msgid "Check the bar-chart to see how your addresses compose your total balance."
+msgstr ""
+
#: src/components/modals/AddressSelectorModal.vue:8
#: src/components/modals/SendModal.vue:61
msgid "Choose a Recipient"
@@ -483,14 +493,22 @@ msgstr ""
msgid "Claiming Cashlink"
msgstr ""
-#: src/components/layouts/Settings.vue:210
+#: src/components/layouts/Settings.vue:224
msgid "Clear"
msgstr ""
-#: src/components/layouts/Settings.vue:204
+#: src/components/layouts/Settings.vue:218
msgid "Clear Cache"
msgstr ""
+#: src/lib/tour/network/NetworkTourTexts.ts:34
+msgid "Click {account_icon} to get back to your wallet."
+msgstr ""
+
+#: src/lib/tour/network/NetworkTourTexts.ts:40
+msgid "Click {back_to_addresses} to get back to your wallet."
+msgstr ""
+
#: src/components/swap/SwapNotification.vue:36
msgid "Click for more information"
msgstr ""
@@ -499,6 +517,10 @@ msgstr ""
msgid "Click on ‘Troubleshooting’ to learn more."
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:20
+msgid "Click the green button to receive a free NIM from Team Nimiq."
+msgstr ""
+
#: src/components/layouts/AddressOverview.vue:151
#: src/components/swap/SwapAnimation.vue:90
msgid "Close"
@@ -524,12 +546,6 @@ msgstr ""
msgid "Congrats"
msgstr ""
-#: src/components/modals/WelcomeModal.vue:15
-msgid ""
-"Connect directly to the Nimiq blockchain.\n"
-"Be independent from any middleman."
-msgstr ""
-
#: src/components/NetworkStats.vue:8
msgid "Connected to"
msgstr ""
@@ -568,7 +584,6 @@ msgstr ""
#: src/components/modals/MigrationWelcomeModal.vue:87
#: src/components/modals/SendModal.vue:87
-#: src/components/modals/WelcomeModal.vue:63
msgid "Continue"
msgstr ""
@@ -613,6 +628,10 @@ msgstr ""
msgid "Create request link"
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:66
+msgid "Create, switch and log out of accounts."
+msgstr ""
+
#: src/components/modals/BtcReceiveModal.vue:194
msgid "Created {count} day ago | Created {count} days ago"
msgstr ""
@@ -641,6 +660,10 @@ msgstr ""
msgid "Currently under maintenance"
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:44
+msgid "Currently you have 100% NIM, and no BTC."
+msgstr ""
+
#: src/components/modals/BuyOptionsModal.vue:7
msgid ""
"Depending on your country of residence,\n"
@@ -651,11 +674,15 @@ msgstr ""
msgid "Developer resources"
msgstr ""
-#: src/components/layouts/Settings.vue:248
+#: src/components/layouts/Settings.vue:262
#: src/components/modals/DisclaimerModal.vue:5
msgid "Disclaimer"
msgstr ""
+#: src/components/modals/DiscoverTheNimiqWalletModal.vue:18
+msgid "Discover the Nimiq Wallet!"
+msgstr ""
+
#: src/components/BtcAddressInput.vue:85
#: src/components/modals/SendModal.vue:355
msgid "Domain does not resolve to a valid address"
@@ -690,7 +717,7 @@ msgstr ""
msgid "Edit contacts"
msgstr ""
-#: src/components/layouts/Settings.vue:125
+#: src/components/layouts/Settings.vue:139
msgid "Edit the amount of decimals visible for BTC values."
msgstr ""
@@ -703,10 +730,20 @@ msgstr ""
msgid "Edit transaction"
msgstr ""
-#: src/components/layouts/Settings.vue:158
+#: src/components/layouts/Settings.vue:172
msgid "Enable experimental support for swiping gestures on mobile."
msgstr ""
+#: src/components/Tour.vue:114
+#: src/lib/tour/network/04_NetworkCompletedStep.ts:32
+msgid "End Tour"
+msgstr ""
+
+#: src/lib/tour/network/NetworkTourTexts.ts:33
+#: src/lib/tour/network/NetworkTourTexts.ts:39
+msgid "Enjoy the decentralized future, and don’t forget to invite your friends and family."
+msgstr ""
+
#: src/components/modals/overlays/SellCryptoBankCheckOverlay.vue:35
msgid "Enter account holder name"
msgstr ""
@@ -730,7 +767,7 @@ msgstr ""
msgid "Enter recipient address..."
msgstr ""
-#: src/components/layouts/Settings.vue:218
+#: src/components/layouts/Settings.vue:232
msgid "Enter the password of the trial you want to enable, then press enter."
msgstr ""
@@ -753,6 +790,11 @@ msgstr ""
msgid "Euro"
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:28
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:33
+msgid "Every NIM address comes with an avatar. They help to make sure you got the right one."
+msgstr ""
+
#: src/components/BtcTransactionListItem.vue:44
#: src/components/TransactionListItem.vue:38
msgid "expired"
@@ -813,7 +855,7 @@ msgstr ""
msgid "Fiat value unavailable"
msgstr ""
-#: src/components/layouts/Settings.vue:325
+#: src/components/layouts/Settings.vue:342
msgid "File contains no contacts."
msgstr ""
@@ -825,6 +867,10 @@ msgstr ""
msgid "Finalizing swap"
msgstr ""
+#: src/lib/tour/network/NetworkTourTexts.ts:25
+msgid "Find the network’s key performance metrics below."
+msgstr ""
+
#: src/components/modals/TradeModal.vue:49
msgid "For beginners"
msgstr ""
@@ -854,6 +900,14 @@ msgstr ""
msgid "Get NIM"
msgstr ""
+#: src/components/layouts/Settings.vue:95
+msgid "Go through the product again"
+msgstr ""
+
+#: src/lib/tour/onboarding/08_OnboardingCompleted.ts:77
+msgid "Go to Network"
+msgstr ""
+
#: src/components/BankCheckInput.vue:92
#: src/components/modals/BtcActivationModal.vue:21
#: src/components/swap/OasisLaunchModal.vue:26
@@ -864,14 +918,18 @@ msgstr ""
msgid "Got it!"
msgstr ""
-#: src/components/modals/WelcomeModal.vue:5
-msgid "Great, you’re here!"
-msgstr ""
-
#: src/components/modals/MigrationWelcomeModal.vue:29
msgid "Handling multiple addresses is now convenient and easy – with one password and shared login information."
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:32
+msgid "Here are your transactions."
+msgstr ""
+
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:27
+msgid "Here’s your first transaction with your first NIM."
+msgstr ""
+
#: src/components/modals/BtcTransactionModal.vue:200
#: src/components/modals/BtcTransactionModal.vue:226
#: src/components/modals/TransactionModal.vue:196
@@ -984,15 +1042,15 @@ msgstr ""
msgid "Is your bank eligible?"
msgstr ""
-#: src/components/modals/WelcomeModal.vue:53
-msgid "It is fast, safe and makes you truly independent."
+#: src/components/modals/DiscoverTheNimiqWalletModal.vue:20
+msgid "It's free, does not collect data and is controlled by no one but you."
msgstr ""
#: src/components/swap/SwapNotification.vue:39
msgid "It's safe to close your wallet now"
msgstr ""
-#: src/components/layouts/Settings.vue:171
+#: src/components/layouts/Settings.vue:185
msgid "Keyguard Mode"
msgstr ""
@@ -1004,10 +1062,6 @@ msgstr ""
msgid "Language"
msgstr ""
-#: src/components/modals/NetworkInfoModal.vue:19
-msgid "Learn more"
-msgstr ""
-
#: src/components/modals/MigrationWelcomeModal.vue:61
msgid "Learn more here"
msgstr ""
@@ -1021,10 +1075,6 @@ msgstr ""
msgid "Let's go"
msgstr ""
-#: src/components/modals/WelcomeModal.vue:64
-msgid "Let's go!"
-msgstr ""
-
#: src/components/NetworkMap.vue:30
msgid "Light Node"
msgstr ""
@@ -1072,7 +1122,7 @@ msgstr ""
msgid "Login File"
msgstr ""
-#: src/components/AccountMenu.vue:53
+#: src/components/modals/AccountMenuModal.vue:26
msgid "Logout"
msgstr ""
@@ -1130,7 +1180,7 @@ msgstr ""
msgid "Name your contact"
msgstr ""
-#: src/components/layouts/Sidebar.vue:50
+#: src/components/layouts/Sidebar.vue:53
msgid "Network"
msgstr ""
@@ -1138,10 +1188,22 @@ msgstr ""
msgid "Network fee: {sats} sat/vByte"
msgstr ""
+#: src/components/Tour.vue:100
+msgid "Next"
+msgstr ""
+
#: src/components/swap/SwapFeesTooltip.vue:39
msgid "NIM network fee"
msgstr ""
+#: src/lib/tour/network/NetworkTourTexts.ts:10
+msgid "Nimiq doesn’t collect or store such data."
+msgstr ""
+
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:80
+msgid "Nimiq is not just any crypto - Click on {network_icon} Network and discover true decentralization."
+msgstr ""
+
#: src/components/modals/DisclaimerModal.vue:17
msgid "Nimiq is not responsible for any loss. Nimiq, wallet.nimiq.com, hub.nimiq.com & keyguard.nimiq.com, and some of the underlying libraries are under active development."
msgstr ""
@@ -1173,11 +1235,11 @@ msgid "No registration – not even email."
msgstr ""
#: src/components/BtcTransactionList.vue:80
-#: src/components/TransactionList.vue:83
+#: src/components/TransactionList.vue:85
msgid "No transactions found"
msgstr ""
-#: src/components/layouts/Settings.vue:130
+#: src/components/layouts/Settings.vue:144
#: src/components/layouts/Settings.vue:70
msgid "None"
msgstr ""
@@ -1213,11 +1275,11 @@ msgstr ""
msgid "OASIS service fee"
msgstr ""
-#: src/components/layouts/Settings.vue:165
+#: src/components/layouts/Settings.vue:179
msgid "Off"
msgstr ""
-#: src/components/layouts/Settings.vue:348
+#: src/components/layouts/Settings.vue:365
msgid "OK! Contacts imported successfully."
msgstr ""
@@ -1225,7 +1287,7 @@ msgstr ""
msgid "Old and new Accounts"
msgstr ""
-#: src/components/layouts/Settings.vue:164
+#: src/components/layouts/Settings.vue:178
msgid "On"
msgstr ""
@@ -1241,7 +1303,7 @@ msgstr ""
msgid "Or buy BTC directly in the wallet."
msgstr ""
-#: src/components/layouts/Settings.vue:173
+#: src/components/layouts/Settings.vue:187
msgid "Overwrite how the Keyguard is opened."
msgstr ""
@@ -1292,14 +1354,18 @@ msgstr ""
msgid "Please transfer"
msgstr ""
-#: src/components/layouts/Settings.vue:178
+#: src/components/layouts/Settings.vue:192
msgid "popups"
msgstr ""
-#: src/components/layouts/Settings.vue:185
+#: src/components/layouts/Settings.vue:199
msgid "Popups"
msgstr ""
+#: src/components/Tour.vue:85
+msgid "Previous"
+msgstr ""
+
#: src/components/TransactionDetailOasisPayoutStatus.vue:30
msgid "Proceed to the troubleshooting page to find out what to do next."
msgstr ""
@@ -1308,6 +1374,10 @@ msgstr ""
msgid "Processing payout"
msgstr ""
+#: src/components/layouts/Settings.vue:93
+msgid "Product Tour"
+msgstr ""
+
#: src/components/swap/SwapSepaFundingInstructions.vue:53
msgid "Purpose"
msgstr ""
@@ -1326,7 +1396,7 @@ msgid "Receive fiat to your bank account."
msgstr ""
#: src/components/TestnetFaucet.vue:11
-#: src/components/TransactionList.vue:79
+#: src/components/TransactionList.vue:81
msgid "Receive free NIM"
msgstr ""
@@ -1334,7 +1404,7 @@ msgstr ""
msgid "Receive NIM"
msgstr ""
-#: src/components/TransactionList.vue:73
+#: src/components/TransactionList.vue:75
msgid "Receive some free NIM to get started."
msgstr ""
@@ -1359,19 +1429,19 @@ msgstr ""
msgid "Recovery Words"
msgstr ""
-#: src/components/layouts/Settings.vue:174
+#: src/components/layouts/Settings.vue:188
msgid "Redirect mode does not yet support swaps."
msgstr ""
-#: src/components/layouts/Settings.vue:178
+#: src/components/layouts/Settings.vue:192
msgid "redirects"
msgstr ""
-#: src/components/layouts/Settings.vue:186
+#: src/components/layouts/Settings.vue:200
msgid "Redirects"
msgstr ""
-#: src/components/layouts/Settings.vue:227
+#: src/components/layouts/Settings.vue:241
msgid "Reference currency"
msgstr ""
@@ -1388,14 +1458,14 @@ msgstr ""
msgid "Regular transactions are too slow and will not process. Your bank might use another name, like ‘real-time transactions’ i.a."
msgstr ""
-#: src/components/layouts/Settings.vue:246
+#: src/components/layouts/Settings.vue:260
#: src/components/UpdateNotification.vue:8
msgid "Release Notes"
msgstr ""
-#: src/components/AccountMenu.vue:43
#: src/components/layouts/AddressOverview.vue:30
#: src/components/layouts/AddressOverview.vue:54
+#: src/components/modals/AccountMenuModal.vue:16
msgid "Rename"
msgstr ""
@@ -1413,7 +1483,7 @@ msgstr ""
msgid "Rescan"
msgstr ""
-#: src/components/layouts/Settings.vue:206
+#: src/components/layouts/Settings.vue:220
msgid "Reset your wallet settings and reload data from the blockchain."
msgstr ""
@@ -1431,7 +1501,7 @@ msgstr ""
msgid "Retrying..."
msgstr ""
-#: src/components/AccountMenu.vue:34
+#: src/components/modals/AccountMenuModal.vue:7
msgid "Save Login File"
msgstr ""
@@ -1459,11 +1529,11 @@ msgstr ""
msgid "Select a currency and an amount."
msgstr ""
-#: src/components/layouts/Settings.vue:140
+#: src/components/layouts/Settings.vue:154
msgid "Select which unit to show Bitcoin amounts in."
msgstr ""
-#: src/components/layouts/Sidebar.vue:35
+#: src/components/layouts/Sidebar.vue:38
msgid "Sell"
msgstr ""
@@ -1503,10 +1573,6 @@ msgstr ""
msgid "Send all"
msgstr ""
-#: src/components/modals/WelcomeModal.vue:50
-msgid "Send and receive NIM directly on the blockchain."
-msgstr ""
-
#: src/components/modals/NetworkInfoModal.vue:16
msgid "Send and receive NIM without a middleman and enjoy true decentralization."
msgstr ""
@@ -1575,6 +1641,10 @@ msgstr ""
msgid "SEPA Instant transaction"
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:56
+msgid "Seriously! There is no ‘forgot password’! Create a backup to make sure you stay in control."
+msgstr ""
+
#: src/components/modals/SendModal.vue:100
#: src/components/modals/SendModal.vue:88
msgid "Set Amount"
@@ -1592,7 +1662,7 @@ msgstr ""
msgid "Setting up the {currency} side of the swap."
msgstr ""
-#: src/components/layouts/Sidebar.vue:58
+#: src/components/layouts/Sidebar.vue:61
msgid "Settings"
msgstr ""
@@ -1618,7 +1688,7 @@ msgstr ""
msgid "Show all my addresses"
msgstr ""
-#: src/components/layouts/Settings.vue:123
+#: src/components/layouts/Settings.vue:137
#: src/components/layouts/Settings.vue:63
msgid "Show Decimals"
msgstr ""
@@ -1627,7 +1697,7 @@ msgstr ""
msgid "Show Link"
msgstr ""
-#: src/components/AccountMenu.vue:39
+#: src/components/modals/AccountMenuModal.vue:12
msgid "Show Recovery Words"
msgstr ""
@@ -1635,6 +1705,10 @@ msgstr ""
msgid "Signup"
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:74
+msgid "Simply click your account and select ‘Create backup’."
+msgstr ""
+
#: src/components/modals/BuyCryptoModal.vue:240
msgid "Simulate EUR payment"
msgstr ""
@@ -1668,8 +1742,16 @@ msgstr ""
msgid "standard"
msgstr ""
-#: src/components/modals/WelcomeModal.vue:7
-msgid "Store, send and receive NIM."
+#: src/components/modals/NetworkInfoModal.vue:19
+msgid "Start Network Tour"
+msgstr ""
+
+#: src/components/layouts/Settings.vue:100
+msgid "Start Tour"
+msgstr ""
+
+#: src/components/modals/DiscoverTheNimiqWalletModal.vue:25
+msgid "Start Wallet tour"
msgstr ""
#. avoid displaying the proxy address until we know related peer address
@@ -1712,7 +1794,7 @@ msgstr ""
msgid "Swap to {address}"
msgstr ""
-#: src/components/layouts/Settings.vue:156
+#: src/components/layouts/Settings.vue:170
msgid "Swiping Gestures"
msgstr ""
@@ -1720,6 +1802,14 @@ msgstr ""
msgid "syncing"
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:14
+msgid "Tap it to open the address details."
+msgstr ""
+
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:61
+msgid "Tap on the menu icon to access your account and wallet settings."
+msgstr ""
+
#: src/components/swap/SwapSepaFundingInstructions.vue:31
msgid "TEN31 Bank provides a bank account to lock your payment."
msgstr ""
@@ -1732,6 +1822,10 @@ msgstr ""
msgid "Testnet"
msgstr ""
+#: src/lib/tour/network/NetworkTourTexts.ts:26
+msgid "The {network_icon}-icon indicates that you are connected to the network."
+msgstr ""
+
#: src/components/swap/SwapSepaFundingInstructions.vue:105
msgid "The bank is processing your transaction."
msgstr ""
@@ -1761,10 +1855,6 @@ msgstr ""
msgid "The new Login Files are an easy and convenient way to gain access to your account and its addresses."
msgstr ""
-#: src/components/modals/WelcomeModal.vue:47
-msgid "The Nimiq wallet is more than a web-wallet, it is a network node."
-msgstr ""
-
#: src/components/modals/BuyCryptoModal.vue:44
#: src/components/modals/SellCryptoModal.vue:43
#: src/components/swap/SwapModal.vue:19
@@ -1791,10 +1881,18 @@ msgstr ""
msgid "There is no ‘forgot password’"
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:53
+msgid "There is no ‘forgot password’. Create a backup to make sure you stay in control."
+msgstr ""
+
#: src/components/swap/SwapNotification.vue:23
msgid "There's a problem"
msgstr ""
+#: src/lib/tour/network/NetworkTourTexts.ts:16
+msgid "These connections enable you to establish consensus with a sub set of participants directly."
+msgstr ""
+
#: src/components/modals/BtcTransactionModal.vue:202
#: src/components/modals/BtcTransactionModal.vue:228
#: src/components/modals/TransactionModal.vue:198
@@ -1810,6 +1908,10 @@ msgstr ""
msgid "This is a Legacy Account"
msgstr ""
+#: src/lib/tour/network/NetworkTourTexts.ts:15
+msgid "This is a peer or a backbone node that you are connected to."
+msgstr ""
+
#: src/components/modals/BtcReceiveModal.vue:49
msgid "This is a single-use address"
msgstr ""
@@ -1822,6 +1924,24 @@ msgstr ""
msgid "This is not a valid IBAN"
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:19
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:22
+msgid "This is where all your transactions will appear."
+msgstr ""
+
+#: src/lib/tour/network/NetworkTourTexts.ts:9
+msgid "This is you. Your location is determined by your IP address."
+msgstr ""
+
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:38
+msgid "This is your Bitcoin wallet. You get one with every Nimiq account."
+msgstr ""
+
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:13
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:9
+msgid "This is your first address, represented by your avatar."
+msgstr ""
+
#: src/components/swap/SwapAnimation.vue:295
#: src/components/swap/SwapAnimation.vue:72
#: src/components/swap/SwapSepaFundingInstructions.vue:106
@@ -1830,8 +1950,8 @@ msgstr ""
#: src/components/BtcTransactionList.vue:238
#: src/components/BtcTransactionList.vue:260
-#: src/components/TransactionList.vue:249
-#: src/components/TransactionList.vue:271
+#: src/components/TransactionList.vue:252
+#: src/components/TransactionList.vue:274
msgid "This month"
msgstr ""
@@ -1899,7 +2019,7 @@ msgstr ""
msgid "Transfer funds"
msgstr ""
-#: src/components/layouts/Settings.vue:216
+#: src/components/layouts/Settings.vue:230
msgid "Trials"
msgstr ""
@@ -1937,10 +2057,6 @@ msgstr ""
msgid "Update now"
msgstr ""
-#: src/components/modals/WelcomeModal.vue:22
-msgid "Use {NIM}, the super performant and browser- based payment coin."
-msgstr ""
-
#: src/components/TransactionDetailOasisPayoutStatus.vue:11
msgid "Use a SEPA Instant account to get payouts in minutes."
msgstr ""
@@ -1957,7 +2073,11 @@ msgstr ""
msgid "Use the slider or edit values to set up a swap."
msgstr ""
-#: src/components/layouts/Settings.vue:192
+#: src/components/TourLargeScreenManager.vue:5
+msgid "Use the tooltips to navigate your tour."
+msgstr ""
+
+#: src/components/layouts/Settings.vue:206
msgid "Vesting Contract"
msgstr ""
@@ -1973,6 +2093,10 @@ msgstr ""
msgid "Waiting for Nimiq network informations"
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:79
+msgid "Wallet tour completed!"
+msgstr ""
+
#: src/components/modals/BuyCryptoModal.vue:14
msgid "Welcome to the first fiat-to-crypto atomic swap. It’s simple, fast and decentralized."
msgstr ""
@@ -1985,7 +2109,7 @@ msgstr ""
msgid "What is your bank called?"
msgstr ""
-#: src/components/layouts/Settings.vue:163
+#: src/components/layouts/Settings.vue:177
msgid "When ready"
msgstr ""
@@ -2018,6 +2142,14 @@ msgstr ""
msgid "You are using a Legacy Account "
msgstr ""
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:73
+msgid "You can always create a new backup."
+msgstr ""
+
+#: src/lib/tour/onboarding/OnboardingTourTexts.ts:10
+msgid "You can click on the address to copy and share it."
+msgstr ""
+
#: src/components/TestnetFaucet.vue:122
msgid "You can receive more free NIM in {waitTime} hours."
msgstr ""
@@ -2030,6 +2162,11 @@ msgstr ""
msgid "You have exceeded your OASIS limit."
msgstr ""
+#: src/lib/tour/network/NetworkTourTexts.ts:31
+#: src/lib/tour/network/NetworkTourTexts.ts:37
+msgid "You made it!"
+msgstr ""
+
#: src/components/modals/overlays/BuyCryptoBankCheckOverlay.vue:6
msgid "You need a SEPA account that supports instant transactions."
msgstr ""
@@ -2081,10 +2218,6 @@ msgstr ""
msgid "Your browser does not support Keyguard popups, or they are disabled in the Settings."
msgstr ""
-#: src/components/modals/WelcomeModal.vue:13
-msgid "Your browser is a Node"
-msgstr ""
-
#: src/components/swap/SwapAnimation.vue:98
#: src/components/TransactionDetailOasisPayoutStatus.vue:20
msgid "Your EUR will be transferred to your bank account as soon as new limit is available."
@@ -2117,6 +2250,6 @@ msgid "Your transaction must be SEPA Instant"
msgstr ""
#: src/components/BtcTransactionList.vue:65
-#: src/components/TransactionList.vue:72
+#: src/components/TransactionList.vue:74
msgid "Your transactions will appear here"
msgstr ""
diff --git a/src/lib/tour/index.ts b/src/lib/tour/index.ts
new file mode 100644
index 000000000..52a23b6b9
--- /dev/null
+++ b/src/lib/tour/index.ts
@@ -0,0 +1,75 @@
+import { ScreenTypes } from '@/composables/useWindowSize';
+import { SetupContext } from '@vue/composition-api';
+import { getNetworkTourSteps } from './network';
+import { getOnboardingTourSteps } from './onboarding';
+import { ITooltipModifier, ITourStep, ITourSteps, NetworkTourStep, OnboardingTourStep, TourName } from './types';
+
+// see more about tooltip modifiers at popper official documentation
+export const defaultTooltipModifiers: ITooltipModifier[] = [
+ {
+ name: 'arrow',
+ options: {
+ element: '.v-step__arrow',
+ padding: 16,
+ },
+ },
+ {
+ name: 'preventOverflow',
+ options: {
+ rootBoundary: 'window',
+ },
+ },
+ {
+ name: 'offset',
+ options: {
+ offset: [0, 10],
+ },
+ },
+];
+
+export function getTour(tour: TourName | undefined, context: SetupContext, screenTypes: ScreenTypes)
+ : ITourStep[] {
+ let steps: ITourSteps = {};
+ switch (tour) {
+ case 'onboarding':
+ steps = getOnboardingTourSteps(context, screenTypes);
+ break;
+ case 'network':
+ steps = getNetworkTourSteps(context, screenTypes);
+ break;
+ default:
+ }
+
+ // At this moment we have something like this:
+ // {
+ // "1": { /** First step object */},
+ // "10": { /** This is not the first step object!! */},
+ // "2": { /** Second step object */},
+ // ...
+ // }
+ // where the key is the step index and the value is the step object that we need to sort
+ // and store it as an array
+ return Object.entries(steps)
+ .filter(([, s]) => Boolean(s)) // Remove undefined steps
+ .sort(([a], [b]) => parseInt(a, 10) - parseInt(b, 10)) // Sort by key
+ .map(([, s]) => s) as ITourStep[]; // Just return only the step
+}
+
+// Finds the component instance given its name in the Vue tree
+// Components in the wallet should not change its logic to fit tour requirements. For example, adding
+// listeners for the tour in AddressList or TransactionList components should be done in the tour
+// and no in each component. In order to access the component instance, we need to find it in the
+// Vue tree.
+export function searchComponentByName(c: Vue, name: string): Vue | undefined {
+ if (c.$options.name === name) {
+ return c;
+ }
+ for (const cc of c.$children) {
+ const found = searchComponentByName(cc, name);
+ if (found) return found;
+ }
+
+ return undefined;
+}
+
+export * from './types';
diff --git a/src/lib/tour/network/01_YourLocationStep.ts b/src/lib/tour/network/01_YourLocationStep.ts
new file mode 100644
index 000000000..8a66f9c04
--- /dev/null
+++ b/src/lib/tour/network/01_YourLocationStep.ts
@@ -0,0 +1,65 @@
+import { defaultTooltipModifiers, INetworkGetStepFnArgs, ITourStep, IWalletHTMLElements, NetworkTourStep } from '..';
+import { getNetworkTexts } from './NetworkTourTexts';
+
+export function getYourLocationStep(
+ { isLargeScreen, nodes, scrollIntoView, sleep, selfNodeIndex }: INetworkGetStepFnArgs): ITourStep {
+ return {
+ path: '/network',
+ tooltip: {
+ target: `${IWalletHTMLElements.NETWORK_NODES} span:nth-child(${selfNodeIndex + 1})`,
+ content: getNetworkTexts(NetworkTourStep.YOUR_LOCATION).default,
+ params: {
+ get placement() {
+ const { position } = nodes()[selfNodeIndex] || { position: undefined };
+ if (!position) return 'bottom';
+ if (isLargeScreen.value) {
+ // If node is far away in the eastern hemisphere, the tooltip will be on the left
+ return position.x > 80 ? 'left' : 'right';
+ }
+ // If node is far away in the south hemisphere, the tooltip will be on the top
+ return position.y > 25 ? 'top' : 'bottom';
+ },
+ get modifiers() {
+ return [
+ {
+ name: 'offset',
+ options: {
+ offset: isLargeScreen.value ? [0, 12] : [0, 16],
+ },
+ },
+ ...defaultTooltipModifiers.filter((d) => d.name !== 'offset'),
+ ];
+ },
+ },
+ },
+ ui: {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+ IWalletHTMLElements.NETWORK_STATS,
+ ],
+ disabledElements: [
+ IWalletHTMLElements.NETWORK_TABLET_MENU_BAR,
+ IWalletHTMLElements.NETWORK_MAP,
+ ],
+ disabledButtons: [IWalletHTMLElements.BUTTON_SIDEBAR_BUY, IWalletHTMLElements.BUTTON_SIDEBAR_SELL],
+ scrollLockedElements: [
+ IWalletHTMLElements.NETWORK_SCROLLER,
+ ],
+ },
+ lifecycle: {
+ created: (async ({ goingForward }) => {
+ // Scroll to the user's node
+ scrollIntoView(nodes()[selfNodeIndex].x);
+ if (!goingForward) {
+ await sleep(500);
+ }
+ }),
+ },
+ } as ITourStep;
+}
diff --git a/src/lib/tour/network/02_BackboneNodeStep.ts b/src/lib/tour/network/02_BackboneNodeStep.ts
new file mode 100644
index 000000000..ef54eb7a2
--- /dev/null
+++ b/src/lib/tour/network/02_BackboneNodeStep.ts
@@ -0,0 +1,93 @@
+import { ref } from '@vue/composition-api';
+import { defaultTooltipModifiers, INetworkGetStepFnArgs, ITourStep, IWalletHTMLElements, NetworkTourStep } from '..';
+import { getNetworkTexts } from './NetworkTourTexts';
+
+export function getBackboneNodeStep(
+ { nodes, selfNodeIndex, isLargeScreen, scrollIntoView, sleep }: INetworkGetStepFnArgs): ITourStep {
+ const selectedNode = ref(-1);
+ return {
+ path: '/network',
+ tooltip: {
+ get target() {
+ return `${IWalletHTMLElements.NETWORK_NODES} span:nth-child(${selectedNode.value + 1})`;
+ },
+ content: getNetworkTexts(NetworkTourStep.BACKBONE_NODE).default,
+ params: {
+ get placement() {
+ const { position } = nodes()[selectedNode.value] || { position: undefined };
+ if (!position) return 'bottom';
+ if (isLargeScreen.value) {
+ // If node is far away in the eastern hemisphere, the tooltip will be on the left
+ return position.x > 100 ? 'left' : 'right';
+ }
+ // If node is far away in the south hemisphere, the tooltip will be on the top
+ return position.y > 25 ? 'top' : 'bottom';
+ },
+ get modifiers() {
+ return [
+ {
+ name: 'offset',
+ options: {
+ offset: [0, 16],
+ },
+ },
+ ...defaultTooltipModifiers.filter((d) => d.name !== 'offset'),
+ ];
+ },
+ },
+ },
+ ui: {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+ IWalletHTMLElements.NETWORK_STATS,
+ ],
+ disabledElements: [
+ IWalletHTMLElements.NETWORK_TABLET_MENU_BAR,
+ IWalletHTMLElements.NETWORK_MAP,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_SIDEBAR_BUY,
+ IWalletHTMLElements.BUTTON_SIDEBAR_SELL,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.NETWORK_SCROLLER,
+ ],
+ },
+ lifecycle: {
+ created: (async () => {
+ // euclidean distance
+ const distance = ([x1, y1]: number[], [x2, y2]: number[]) => (((x1 - x2) ** 2) + (y1 - y2) ** 2) ** 0.5;
+ const _nodes = nodes();
+ const { position: pSelf } = _nodes[selfNodeIndex];
+
+ // get closest west node minimum distance of 5.
+ // we prioritize west nodes so tooltip is shown on the right
+ const node = _nodes
+ // add index
+ .map((n, i) => ({ ...n, i, x: n.x }))
+ // remove self
+ .filter((_, i) => i !== selfNodeIndex)
+ // compute distance
+ .map((n) => ({ ...n, d: distance([n.position.x, n.position.y], [pSelf.x, pSelf.y]) }))
+ // at least 5 distance away
+ .filter(({ d }) => d > 5)
+ // get closest
+ .sort((a, b) => a.d - b.d)[0];
+
+ // get the index so we can select the correct child in the dom to show the tooltip
+ selectedNode.value = node.i;
+
+ // Scroll to the selected node
+ scrollIntoView(node.x);
+
+ await sleep(500);
+ }),
+ },
+ } as ITourStep;
+}
diff --git a/src/lib/tour/network/03_NetworkMetricsStep.ts b/src/lib/tour/network/03_NetworkMetricsStep.ts
new file mode 100644
index 000000000..61c9b85f7
--- /dev/null
+++ b/src/lib/tour/network/03_NetworkMetricsStep.ts
@@ -0,0 +1,49 @@
+import { NetworkTourStep, ITourStep, IWalletHTMLElements, defaultTooltipModifiers } from '..';
+import { getNetworkTexts } from './NetworkTourTexts';
+
+export function getNetworkMetricsStep(): ITourStep {
+ return {
+ path: '/network',
+ tooltip: {
+ target: IWalletHTMLElements.NETWORK_STATS,
+ content: getNetworkTexts(NetworkTourStep.METRICS).default,
+ params: {
+ placement: 'top',
+ get modifiers() {
+ return [
+ {
+ name: 'offset',
+ options: {
+ offset: [0, 16],
+ },
+ },
+ ...defaultTooltipModifiers.filter((d) => d.name !== 'offset'),
+ ];
+ },
+ },
+ },
+ ui: {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+ ],
+ disabledElements: [
+ IWalletHTMLElements.NETWORK_TABLET_MENU_BAR,
+ IWalletHTMLElements.NETWORK_MAP,
+ IWalletHTMLElements.NETWORK_STATS,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_SIDEBAR_BUY,
+ IWalletHTMLElements.BUTTON_SIDEBAR_SELL,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.NETWORK_SCROLLER,
+ ],
+ },
+ } as ITourStep;
+}
diff --git a/src/lib/tour/network/04_NetworkCompletedStep.ts b/src/lib/tour/network/04_NetworkCompletedStep.ts
new file mode 100644
index 000000000..34ce0db2e
--- /dev/null
+++ b/src/lib/tour/network/04_NetworkCompletedStep.ts
@@ -0,0 +1,64 @@
+import { INetworkGetStepFnArgs, NetworkTourStep, ITourStep, IWalletHTMLElements, defaultTooltipModifiers } from '..';
+import { getNetworkTexts } from './NetworkTourTexts';
+
+export function getNetworkCompletedStep({ root, isLargeScreen }: INetworkGetStepFnArgs): ITourStep {
+ return {
+ path: '/network',
+ tooltip: {
+ get target() {
+ return isLargeScreen.value
+ ? IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU
+ : `${IWalletHTMLElements.NETWORK_TABLET_MENU_BAR} .account-button`;
+ },
+ content: getNetworkTexts(NetworkTourStep.NETWORK_COMPLETED)[
+ isLargeScreen.value ? 'default' : 'alternative'],
+ params: {
+ get placement() {
+ return isLargeScreen.value ? 'right' : 'top';
+ },
+ get modifiers() {
+ return [
+ {
+ name: 'offset',
+ options: {
+ offset: [0, 16],
+ },
+ },
+ ...defaultTooltipModifiers.filter((d) => d.name !== 'offset'),
+ ];
+ },
+ },
+ button: {
+ text: root.$t('End Tour'),
+ fn: async (endTour) => {
+ if (endTour) {
+ await endTour();
+ }
+ },
+ },
+ },
+ ui: {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+ ],
+ disabledElements: [
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.NETWORK_TABLET_MENU_BAR,
+ IWalletHTMLElements.NETWORK_MAP,
+ IWalletHTMLElements.NETWORK_STATS,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_SIDEBAR_BUY,
+ IWalletHTMLElements.BUTTON_SIDEBAR_SELL,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.NETWORK_SCROLLER,
+ ],
+ },
+ } as ITourStep;
+}
diff --git a/src/lib/tour/network/NetworkTourTexts.ts b/src/lib/tour/network/NetworkTourTexts.ts
new file mode 100644
index 000000000..d23451f67
--- /dev/null
+++ b/src/lib/tour/network/NetworkTourTexts.ts
@@ -0,0 +1,47 @@
+import { IContentSpecialItem, ITourStepTexts, NetworkTourStep } from '../types';
+
+// This is used to trick the translation extraction script into extracting those strings
+const $t = (s: string) => s;
+
+const texts: ITourStepTexts = {
+ [NetworkTourStep.YOUR_LOCATION]: {
+ default: [
+ $t('This is you. Your location is determined by your IP address.'),
+ $t('Nimiq doesn’t collect or store such data.'),
+ ],
+ },
+ [NetworkTourStep.BACKBONE_NODE]: {
+ default: [
+ $t('This is a peer or a backbone node that you are connected to.'),
+ $t('These connections enable you to establish consensus with a sub set of participants directly.'),
+ [
+ $t('‘Available browsers’ are other user’s browsers, just like yours.'),
+ $t('‘Backbone nodes’ provide a fallback to connect to.'),
+ ],
+ ],
+ },
+ [NetworkTourStep.METRICS]: {
+ default: [
+ $t('Find the network’s key performance metrics below.'),
+ $t('The {network_icon}-icon indicates that you are connected to the network.'),
+ ],
+ },
+ [NetworkTourStep.NETWORK_COMPLETED]: {
+ default: [
+ $t('You made it!'),
+ IContentSpecialItem.HR,
+ $t('Enjoy the decentralized future, and don’t forget to invite your friends and family.'),
+ $t('Click {account_icon} to get back to your wallet.'),
+ ],
+ alternative: [
+ $t('You made it!'),
+ IContentSpecialItem.HR,
+ $t('Enjoy the decentralized future, and don’t forget to invite your friends and family.'),
+ $t('Click {back_to_addresses} to get back to your wallet.'),
+ ],
+ },
+};
+
+export function getNetworkTexts(i: NetworkTourStep) {
+ return texts[i];
+}
diff --git a/src/lib/tour/network/index.ts b/src/lib/tour/network/index.ts
new file mode 100644
index 000000000..80e2e1c35
--- /dev/null
+++ b/src/lib/tour/network/index.ts
@@ -0,0 +1,42 @@
+import { ScreenTypes } from '@/composables/useWindowSize';
+import { NodeHexagon, NodeType, SCALING_FACTOR, WIDTH } from '@/lib/NetworkMap';
+import { SetupContext } from '@vue/composition-api';
+import { searchComponentByName } from '..';
+import { INetworkGetStepFnArgs, ITourSteps, IWalletHTMLElements, NetworkTourStep } from '../types';
+import { getYourLocationStep } from './01_YourLocationStep';
+import { getBackboneNodeStep } from './02_BackboneNodeStep';
+import { getNetworkMetricsStep } from './03_NetworkMetricsStep';
+import { getNetworkCompletedStep } from './04_NetworkCompletedStep';
+
+export function getNetworkTourSteps({ root }: SetupContext, screenTypes: ScreenTypes): ITourSteps {
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
+
+ const networkMapInstance = searchComponentByName(root, 'network-map') as any;
+ const nodes = () => networkMapInstance?.nodes as NodeHexagon[] || [];
+ const selfNodeIndex = nodes().findIndex((node) => [...node.peers].some((peer) => peer.type === NodeType.SELF));
+
+ // computes how far the x value is from the left border and then scrolls to it
+ const scrollIntoView = async (x: number) => {
+ const map = document.querySelector(IWalletHTMLElements.NETWORK_SCROLLER) as HTMLElement;
+ const mapWidth = map.children[0]!.clientWidth;
+ const adjustedX = (x / 2) * SCALING_FACTOR * (mapWidth / WIDTH);
+ const scrollTarget = adjustedX - (window.innerWidth / 2);
+ map.scrollTo(scrollTarget, 0);
+ };
+
+ const args: INetworkGetStepFnArgs = {
+ nodes,
+ selfNodeIndex,
+ ...screenTypes,
+ scrollIntoView,
+ sleep,
+ root,
+ };
+
+ return {
+ [NetworkTourStep.YOUR_LOCATION]: getYourLocationStep(args),
+ [NetworkTourStep.BACKBONE_NODE]: getBackboneNodeStep(args),
+ [NetworkTourStep.METRICS]: getNetworkMetricsStep(),
+ [NetworkTourStep.NETWORK_COMPLETED]: getNetworkCompletedStep(args),
+ };
+}
diff --git a/src/lib/tour/onboarding/01_FirstAddressStep.ts b/src/lib/tour/onboarding/01_FirstAddressStep.ts
new file mode 100644
index 000000000..bfd5217d5
--- /dev/null
+++ b/src/lib/tour/onboarding/01_FirstAddressStep.ts
@@ -0,0 +1,144 @@
+import { CryptoCurrency } from '@/lib/Constants';
+import { useAccountStore } from '@/stores/Account';
+import { useAddressStore } from '@/stores/Address';
+import { defaultTooltipModifiers } from '..';
+import {
+ ILifecycleArgs,
+ IOnboardingGetStepFnArgs, ITourStep,
+ IWalletHTMLElements, OnboardingTourStep,
+} from '../types';
+import { getOnboardingTexts } from './OnboardingTourTexts';
+
+export function getFirstAddressStep(
+ { isSmallScreen, root, toggleHighlightButton }: IOnboardingGetStepFnArgs): ITourStep {
+ const { setActiveCurrency } = useAccountStore();
+ const { addressInfos, selectAddress } = useAddressStore();
+
+ const ui: ITourStep['ui'] = {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_TRADE_ACTIONS,
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BACKUP_ALERT,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_TABLET_MENU_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BALANCE,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BITCOIN,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_MOBILE_ACTION_BAR,
+
+ ...(!isSmallScreen.value
+ ? [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ ] : [
+ ...Array.from({ length: addressInfos.value.length }).map(
+ (_, i) => `${IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST} button:nth-child(${i + 3})`),
+ ]
+ ),
+ ],
+ disabledElements: [
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS_MOBILE,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_SIDEBAR_BUY,
+ IWalletHTMLElements.BUTTON_SIDEBAR_SELL,
+ IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_BUY,
+ IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_RECEIVE_FREE_NIM,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} .vue-recycle-scroller`,
+ ],
+ explicitInteractableElements: [
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS} .copyable`,
+ ],
+ };
+
+ const path = '/';
+
+ // User must be able to click the first address to open it in the address overview manually
+ const mountedFnForSmallScreen = ({ goToNextStep }: ILifecycleArgs) => {
+ const button = `${IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST} button:nth-child(2)`;
+ toggleHighlightButton(button, true, 'gray');
+
+ const addressButton = document.querySelector(button) as HTMLButtonElement;
+
+ let addressClicked = false;
+ const onClick = (e: MouseEvent) => {
+ addressClicked = true;
+ goToNextStep();
+ e.preventDefault();
+ e.stopPropagation();
+ };
+
+ addressButton!.addEventListener('click', onClick, { once: true, capture: true });
+
+ return async (args: Omit) => {
+ if (!args?.ending && !addressClicked && root.$route.path === path) {
+ // If user clicked the 'next step' button, then we trigger the click on the first address
+ addressButton!.click();
+ await root.$nextTick();
+ }
+ addressButton!.removeEventListener('click', onClick, true);
+ setTimeout(() => toggleHighlightButton(button, false, 'gray'), 500);
+ };
+ };
+
+ const mountedFnForNotSmallScreen = () => {
+ // Not show identicon menu when user hover the active address
+ const identiconMenu = document.querySelector(
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS} .identicon-wrapper .identicon-menu`) as HTMLElement;
+ identiconMenu.style.display = 'none';
+ return () => {
+ identiconMenu.style.display = 'flex';
+ };
+ };
+
+ return {
+ path,
+ tooltip: {
+ get target() {
+ return isSmallScreen.value
+ ? `${IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST} .address-button .identicon img`
+ : `${IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS} .identicon`;
+ },
+ get content() {
+ return getOnboardingTexts(OnboardingTourStep.FIRST_ADDRESS)[
+ isSmallScreen.value ? 'alternative' : 'default'];
+ },
+ params: {
+ get placement() {
+ return isSmallScreen.value ? 'bottom-start' : 'left-start';
+ },
+ get modifiers() {
+ return [
+ {
+ name: 'offset',
+ options: {
+ offset: isSmallScreen.value ? [0, 12] : [0, 20],
+ },
+ },
+ ...defaultTooltipModifiers.filter((d) => d.name !== 'offset'),
+ ];
+ },
+ },
+ },
+ lifecycle: {
+ created: () => {
+ // Select first NIM address as active
+ setActiveCurrency(CryptoCurrency.NIM);
+ selectAddress(addressInfos.value[0].address);
+ },
+ mounted: (args) => isSmallScreen.value ? mountedFnForSmallScreen(args) : mountedFnForNotSmallScreen(),
+ },
+ ui,
+ } as ITourStep;
+}
diff --git a/src/lib/tour/onboarding/02_TransactionListStep.ts b/src/lib/tour/onboarding/02_TransactionListStep.ts
new file mode 100644
index 000000000..86527dc9c
--- /dev/null
+++ b/src/lib/tour/onboarding/02_TransactionListStep.ts
@@ -0,0 +1,145 @@
+import { ref, watch } from '@vue/composition-api';
+import { defaultTooltipModifiers, ITooltipModifier, IWalletHTMLElements } from '..';
+import { IOnboardingGetStepFnArgs, ITourStep, OnboardingTourStep } from '../types';
+import { getOnboardingTexts } from './OnboardingTourTexts';
+
+export function getTransactionListStep(
+ { isSmallScreen, toggleHighlightButton, txsLen }: IOnboardingGetStepFnArgs): ITourStep {
+ const freeNimBtn = ref(() =>
+ document.querySelector(IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_RECEIVE_FREE_NIM) as HTMLDivElement);
+
+ const ui: ITourStep['ui'] = {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_TRADE_ACTIONS,
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_MOBILE_ACTION_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BACKUP_ALERT,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_TABLET_MENU_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BALANCE,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BITCOIN,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledElements: [
+ ...(txsLen.value > 0
+ ? [IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS]
+ : [
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} h2`,
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} span`,
+ ]
+ ),
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS_MOBILE,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_SIDEBAR_BUY,
+ IWalletHTMLElements.BUTTON_SIDEBAR_SELL,
+ txsLen.value === 0 ? '' : IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_BUY,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} .vue-recycle-scroller`,
+ ],
+ explicitInteractableElements: [
+ txsLen.value === 0 ? IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_BUY : '',
+ ],
+ };
+
+ return {
+ get path() {
+ return isSmallScreen.value ? '/transactions' : '/';
+ },
+ tooltip: {
+ get target() {
+ if (txsLen.value > 0) {
+ return isSmallScreen.value
+ ? `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS}
+ .vue-recycle-scroller__item-view:nth-child(2)`
+ : '.address-overview';
+ }
+ return isSmallScreen.value
+ ? `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} > .empty-state h2`
+ : '.address-overview';
+ },
+ get content() {
+ const textType = txsLen.value === 0 && freeNimBtn.value ? 'default' : 'alternative';
+ return getOnboardingTexts(OnboardingTourStep.TRANSACTION_LIST)[textType] || [];
+ },
+ params: {
+ get placement() {
+ if (!isSmallScreen.value) {
+ return 'left';
+ }
+ return txsLen.value > 0 ? 'bottom' : 'top';
+ },
+ get modifiers() {
+ return [
+ {
+ name: 'offset',
+ options: {
+ offset: txsLen.value > 0 && isSmallScreen.value ? [0, 32] : [0, 20],
+ },
+ } as ITooltipModifier,
+ ...defaultTooltipModifiers.filter((d) => d.name !== 'offset'),
+ ];
+ },
+ },
+ },
+ lifecycle: {
+ mounted: ({ goToNextStep, isNextStepDisabled }) => {
+ if (txsLen.value > 0) return undefined;
+
+ function freeNimBtnClickHandler() {
+ setTimeout(() => {
+ // User in the mainnet is expected to click on the 'Get Free NIM' button
+ // This button opens the faucet page and the user exits the wallet. Once the user finishes, he
+ // is expected to return to the wallet and automatically the tour will go to the next step.
+ // There might be a possibility that the user gets an error in the faucet, and therefore, the
+ // tour will not continue. In those cases we will let the user click 'Next step' but it will
+ // actually skip the third step and go to the fourth step
+ // FIXME: In the future, if we integrate the faucet into the wallet, we could improve this
+ // behaviour
+ if (txsLen.value > 0) return;
+ isNextStepDisabled.value = false;
+ }, 2000);
+ }
+
+ if (freeNimBtn.value() !== null) {
+ freeNimBtn.value().addEventListener('click', freeNimBtnClickHandler, { once: true });
+ } else {
+ isNextStepDisabled.value = false;
+ }
+
+ const unwatch = watch(txsLen, (newVal) => {
+ if (newVal > 0) goToNextStep();
+ });
+
+ // Add hightlight effect to 'Get Free NIM' button. This will be ignored if the user have at least one tx
+ toggleHighlightButton(IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_RECEIVE_FREE_NIM, true, 'green');
+ return () => {
+ unwatch();
+ if (freeNimBtn.value()) {
+ freeNimBtn.value().removeEventListener('click', freeNimBtnClickHandler);
+ }
+ return toggleHighlightButton(
+ IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_RECEIVE_FREE_NIM, false, 'green');
+ };
+ },
+ },
+ get ui() {
+ return {
+ ...ui,
+
+ // User is expected to click on the 'Get Free NIM' button if they have not txs
+ isNextStepDisabled: txsLen.value === 0,
+ };
+ },
+ };
+}
diff --git a/src/lib/tour/onboarding/03_FirstTransactionStep.ts b/src/lib/tour/onboarding/03_FirstTransactionStep.ts
new file mode 100644
index 000000000..5f8afb247
--- /dev/null
+++ b/src/lib/tour/onboarding/03_FirstTransactionStep.ts
@@ -0,0 +1,87 @@
+import { defaultTooltipModifiers, IWalletHTMLElements } from '..';
+import { IOnboardingGetStepFnArgs, ITooltipModifier, ITourStep, OnboardingTourStep } from '../types';
+import { getOnboardingTexts } from './OnboardingTourTexts';
+
+export function getFirstTransactionStep({ isSmallScreen, txsLen }: IOnboardingGetStepFnArgs): ITourStep {
+ const ui: ITourStep['ui'] = {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_TRADE_ACTIONS,
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_MOBILE_ACTION_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BACKUP_ALERT,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_TABLET_MENU_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BALANCE,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BITCOIN,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledElements: [
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS_MOBILE,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_SIDEBAR_BUY,
+ IWalletHTMLElements.BUTTON_SIDEBAR_SELL,
+ IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_BUY,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} .vue-recycle-scroller`,
+ ],
+ };
+
+ return {
+ get path() {
+ return isSmallScreen.value ? '/transactions' : '/';
+ },
+ lifecycle: {
+ created: ({ goToNextStep, goToPrevStep, goingForward }) => {
+ // Might be the case that user has no transactions yet and still he reaches this step nonetheless.
+ // This is because if NIM Faucet fails, we let the user continue to the next step. If that happens,
+ // we need to skip this step and go to the next/prev one
+ if (txsLen.value === 0) {
+ if (goingForward) {
+ goToNextStep({ withDelay: false });
+ } else {
+ goToPrevStep({ withDelay: false });
+ }
+ }
+ },
+ },
+ tooltip: {
+ get target() {
+ return `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} ${isSmallScreen.value
+ ? '.transaction > .identicon'
+ : '.vue-recycle-scroller__item-view:nth-child(2)'}`;
+ },
+ content: getOnboardingTexts(OnboardingTourStep.FIRST_TRANSACTION)[
+ txsLen.value === 1 ? 'default' : 'alternative'],
+ params: {
+ get placement() {
+ return isSmallScreen.value ? 'bottom-start' : 'left';
+ },
+ get modifiers() {
+ return [
+ {
+ name: 'offset',
+ options: {
+ offset: isSmallScreen.value ? [0, 10] : [0, -4],
+ },
+ } as ITooltipModifier,
+ ...defaultTooltipModifiers.filter((d) => d.name !== 'offset'),
+ ];
+ },
+ },
+ },
+ ui,
+ } as ITourStep;
+}
diff --git a/src/lib/tour/onboarding/04_BitcoinAddressStep.ts b/src/lib/tour/onboarding/04_BitcoinAddressStep.ts
new file mode 100644
index 000000000..b1a7ceb9a
--- /dev/null
+++ b/src/lib/tour/onboarding/04_BitcoinAddressStep.ts
@@ -0,0 +1,86 @@
+import { defaultTooltipModifiers } from '..';
+import {
+ IOnboardingGetStepFnArgs,
+ ITooltipModifier,
+ ITourStep,
+ IWalletHTMLElements,
+ OnboardingTourStep,
+} from '../types';
+import { getOnboardingTexts } from './OnboardingTourTexts';
+
+export function getBitcoinAddressStep(
+ { isSmallScreen, isMediumScreen, isLargeScreen }: IOnboardingGetStepFnArgs): ITourStep {
+ const ui: ITourStep['ui'] = {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_TRADE_ACTIONS,
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_MOBILE_ACTION_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BACKUP_ALERT,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_TABLET_MENU_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BALANCE,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BITCOIN,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS_MOBILE,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_SIDEBAR_BUY,
+ IWalletHTMLElements.BUTTON_SIDEBAR_SELL,
+ IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_BUY,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} .vue-recycle-scroller`,
+ ],
+ };
+
+ return {
+ path: '/',
+ tooltip: {
+ get target() {
+ return `.account-overview .bitcoin-account ${!isLargeScreen.value
+ ? '> .bitcoin-account-item > svg' : ''}`;
+ },
+ content: getOnboardingTexts(OnboardingTourStep.BITCOIN_ADDRESS).default,
+ params: {
+ get placement() {
+ return !isLargeScreen.value ? 'bottom-start' : 'left-end';
+ },
+ get modifiers() {
+ let offset;
+ if (isSmallScreen.value) offset = [0, 40];
+ else if (isMediumScreen.value) offset = [0, 20];
+ else offset = [0, 40];
+
+ return [
+ {
+ name: 'preventOverflow',
+ options: {
+ altAxis: false,
+ padding: 16,
+ },
+ },
+ {
+ name: 'offset',
+ options: { offset },
+ } as ITooltipModifier,
+ ...defaultTooltipModifiers.filter(({ name }) => !['offset', 'preventOverflow'].includes(name)),
+ ];
+ },
+ },
+ },
+ ui,
+ } as ITourStep;
+}
diff --git a/src/lib/tour/onboarding/05_WalletBalanceStep.ts b/src/lib/tour/onboarding/05_WalletBalanceStep.ts
new file mode 100644
index 000000000..3b6566e65
--- /dev/null
+++ b/src/lib/tour/onboarding/05_WalletBalanceStep.ts
@@ -0,0 +1,82 @@
+import { useAddressStore } from '@/stores/Address';
+import { useBtcAddressStore } from '@/stores/BtcAddress';
+import { defaultTooltipModifiers } from '..';
+import { IOnboardingGetStepFnArgs, ITourStep, IWalletHTMLElements, OnboardingTourStep } from '../types';
+import { getOnboardingTexts } from './OnboardingTourTexts';
+
+export function getWalletBalanceStep({ isSmallScreen }: IOnboardingGetStepFnArgs): ITourStep {
+ const ui: ITourStep['ui'] = {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_TRADE_ACTIONS,
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_MOBILE_ACTION_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BACKUP_ALERT,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_TABLET_MENU_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BALANCE,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BITCOIN,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS_MOBILE,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_SIDEBAR_BUY,
+ IWalletHTMLElements.BUTTON_SIDEBAR_SELL,
+ IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_BUY,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} .vue-recycle-scroller`,
+ ],
+ };
+
+ const { accountBalance: btcAccountBalance } = useBtcAddressStore();
+ const { accountBalance: nimAccountBalance } = useAddressStore();
+ const hasOnlyNim = () => nimAccountBalance.value > 0 && btcAccountBalance.value === 0;
+
+ return {
+ path: '/',
+ tooltip: {
+ get target() {
+ return `${IWalletHTMLElements.ACCOUNT_OVERVIEW_BALANCE} .balance-distribution
+ ${!isSmallScreen.value ? '.btc .tooltip .bar' : ''}`;
+ },
+ content: getOnboardingTexts(OnboardingTourStep.WALLET_BALANCE)[hasOnlyNim() ? 'default' : 'alternative'],
+ params: {
+ get placement() {
+ return isSmallScreen.value ? 'bottom' : 'right';
+ },
+ get modifiers() {
+ return [
+ {
+ name: 'preventOverflow',
+ options: {
+ mainAxis: false,
+ padding: 8,
+ },
+ },
+ {
+ name: 'offset',
+ options: {
+ offset: [0, 16],
+ },
+ },
+ ...defaultTooltipModifiers.filter(({ name }) => !['offset', 'preventOverflow'].includes(name)),
+ ];
+ },
+ },
+ },
+ ui,
+ } as ITourStep;
+}
diff --git a/src/lib/tour/onboarding/06_0_BackupAlertStep.ts b/src/lib/tour/onboarding/06_0_BackupAlertStep.ts
new file mode 100644
index 000000000..18372caf5
--- /dev/null
+++ b/src/lib/tour/onboarding/06_0_BackupAlertStep.ts
@@ -0,0 +1,105 @@
+import { useAccountStore } from '@/stores/Account';
+import { watch } from '@vue/composition-api';
+import { defaultTooltipModifiers, ITourOrigin, IWalletHTMLElements, searchComponentByName } from '..';
+import { IOnboardingGetStepFnArgs, ITourStep, OnboardingTourStep } from '../types';
+import { getOnboardingTexts } from './OnboardingTourTexts';
+
+export function getBackupAlertStep(
+ { isSmallScreen, startedFrom, toggleHighlightButton, root }: IOnboardingGetStepFnArgs): ITourStep {
+ const ui: ITourStep['ui'] = {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_TRADE_ACTIONS,
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_MOBILE_ACTION_BAR,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_TABLET_MENU_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BALANCE,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BITCOIN,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS_MOBILE,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_SIDEBAR_BUY,
+ IWalletHTMLElements.BUTTON_SIDEBAR_SELL,
+ IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_BUY,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} .vue-recycle-scroller`,
+ ],
+ };
+ return {
+ path: '/',
+ tooltip: {
+ get target() {
+ return isSmallScreen.value
+ ? `${IWalletHTMLElements.ACCOUNT_OVERVIEW_BACKUP_ALERT} button`
+ : IWalletHTMLElements.ACCOUNT_OVERVIEW_BACKUP_ALERT;
+ },
+ content: getOnboardingTexts(OnboardingTourStep.BACKUP_ALERT)[
+ startedFrom === ITourOrigin.WELCOME_MODAL ? 'default' : 'alternative'] || [],
+ params: {
+ get placement() {
+ return isSmallScreen.value ? 'bottom' : 'right';
+ },
+ get modifiers() {
+ return [
+ {
+ name: 'preventOverflow',
+ options: {
+ altAxis: false,
+ padding: 8,
+ },
+ },
+ {
+ name: 'offset',
+ options: {
+ offset: isSmallScreen.value ? [0, 10] : [0, 16],
+ },
+ },
+ ...defaultTooltipModifiers.filter(({ name }) => !['preventOverflow', 'offset'].includes(name)),
+ ];
+ },
+ },
+ },
+ ui,
+ lifecycle: {
+ mounted: () => {
+ const unwatch = watch(useAccountStore().activeAccountInfo, async (newVal) => {
+ if (newVal?.wordsExported) {
+ // If user clicks "save recover words" and completes the process, we
+ // no longer need to show the user this step, therefore, we execute
+ // setTourAsArray again, and go to the next step which in reality will have
+ // same index
+ const nimiqTourInstance = searchComponentByName(root, 'nimiq-tour') as any;
+ if (!nimiqTourInstance) return;
+
+ nimiqTourInstance.setTourAsArray();
+ nimiqTourInstance.currentStep -= 1;
+
+ setTimeout(() => nimiqTourInstance.goToNextStep(), 500);
+ }
+ });
+
+ // hightlight 'Revover words' button
+ toggleHighlightButton(IWalletHTMLElements.BUTTON_ADDRESS_BACKUP_ALERT, true, 'orange');
+ return () => {
+ unwatch();
+ toggleHighlightButton(IWalletHTMLElements.BUTTON_ADDRESS_BACKUP_ALERT, false, 'orange');
+ };
+ },
+ },
+ } as ITourStep;
+}
diff --git a/src/lib/tour/onboarding/06_1_MenuIconStep.ts b/src/lib/tour/onboarding/06_1_MenuIconStep.ts
new file mode 100644
index 000000000..c3cefa462
--- /dev/null
+++ b/src/lib/tour/onboarding/06_1_MenuIconStep.ts
@@ -0,0 +1,72 @@
+import { defaultTooltipModifiers, IWalletHTMLElements } from '..';
+import { IOnboardingGetStepFnArgs, ITourStep, OnboardingTourStep } from '../types';
+import { getOnboardingTexts } from './OnboardingTourTexts';
+
+export function getMenuIconStep({ toggleHighlightButton }: IOnboardingGetStepFnArgs): ITourStep {
+ return {
+ path: '/',
+ tooltip: {
+ target: `${IWalletHTMLElements.ACCOUNT_OVERVIEW_TABLET_MENU_BAR} > button.reset`,
+ content: getOnboardingTexts(OnboardingTourStep.MENU_ICON).default,
+ params: {
+ placement: 'bottom-start',
+ modifiers: [
+ {
+ name: 'preventOverflow',
+ options: {
+ mainAxis: false,
+ padding: 8,
+ },
+ },
+ {
+ name: 'offset',
+ options: {
+ offset: [-20, 10],
+ },
+ },
+ ...defaultTooltipModifiers.filter(({ name }) => ['offset', 'preventOverflow'].includes(name)),
+ ],
+ },
+ },
+ lifecycle: {
+ mounted: async ({ goToNextStep }) => {
+ const hamburguerIconSelector = `${IWalletHTMLElements.ACCOUNT_OVERVIEW_TABLET_MENU_BAR} > button.reset`;
+ // User is expected to click the hamburguer icon to go to next step
+ const hamburguerIcon = document.querySelector(hamburguerIconSelector) as HTMLButtonElement;
+
+ const onHamburguerClick = () => goToNextStep();
+ hamburguerIcon!.addEventListener('click', onHamburguerClick, true);
+
+ toggleHighlightButton(hamburguerIconSelector, true, 'gray');
+
+ return () => {
+ toggleHighlightButton(hamburguerIconSelector, false, 'gray');
+ hamburguerIcon!.removeEventListener('click', onHamburguerClick, true);
+ };
+ },
+ },
+ ui: {
+ fadedElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BALANCE,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BITCOIN,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS_MOBILE,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_BUY,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} .vue-recycle-scroller`,
+ ],
+ isNextStepDisabled: true,
+ },
+ } as ITourStep;
+}
diff --git a/src/lib/tour/onboarding/07_1_BackupOptionNotLargeScreenStep.ts b/src/lib/tour/onboarding/07_1_BackupOptionNotLargeScreenStep.ts
new file mode 100644
index 000000000..fb63436f6
--- /dev/null
+++ b/src/lib/tour/onboarding/07_1_BackupOptionNotLargeScreenStep.ts
@@ -0,0 +1,70 @@
+import { defaultTooltipModifiers, IWalletHTMLElements } from '..';
+import { IOnboardingGetStepFnArgs, OnboardingTourStep, ITourStep } from '../types';
+import { getOnboardingTexts } from './OnboardingTourTexts';
+
+export function getBackupOptionNotLargeScreenStep({ isSmallScreen }: IOnboardingGetStepFnArgs): ITourStep {
+ const ui: ITourStep['ui'] = {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_TRADE_ACTIONS,
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_MOBILE_ACTION_BAR,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledElements: [
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.SIDEBAR_MOBILE_TAP_AREA,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BACKUP_ALERT,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_TABLET_MENU_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BALANCE,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BITCOIN,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS_MOBILE,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ IWalletHTMLElements.MODAL_CONTAINER,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_SIDEBAR_BUY,
+ IWalletHTMLElements.BUTTON_SIDEBAR_SELL,
+ IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_BUY,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} .vue-recycle-scroller`,
+ ],
+ };
+
+ return {
+ path: '/accounts?sidebar=true',
+ tooltip: {
+ target: `${IWalletHTMLElements.MODAL_PAGE} .current-account .item:nth-child(3)
+ ${isSmallScreen.value ? 'svg path:nth-child(2)' : ''}`,
+ content: getOnboardingTexts(
+ OnboardingTourStep.BACKUP_OPTION_FROM_OPTIONS).default,
+ params: {
+ get placement() {
+ return isSmallScreen.value ? 'top-start' : 'right';
+ },
+ get modifiers() {
+ return [
+ {
+ name: 'offset',
+ options: {
+ offset: isSmallScreen.value ? [-20, 16] : [0, -40],
+ },
+ },
+ ...defaultTooltipModifiers.filter(({ name }) => name !== 'offset'),
+ ];
+ },
+ },
+ },
+ ui,
+ } as ITourStep;
+}
diff --git a/src/lib/tour/onboarding/07_2_BackupOptionLargeScreenStep.ts b/src/lib/tour/onboarding/07_2_BackupOptionLargeScreenStep.ts
new file mode 100644
index 000000000..bd863244c
--- /dev/null
+++ b/src/lib/tour/onboarding/07_2_BackupOptionLargeScreenStep.ts
@@ -0,0 +1,63 @@
+import { defaultTooltipModifiers, IWalletHTMLElements } from '..';
+import { OnboardingTourStep, ITourStep } from '../types';
+import { getOnboardingTexts } from './OnboardingTourTexts';
+
+export function getBackupOptionLargeScreenStep(): ITourStep {
+ const ui: ITourStep['ui'] = {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_TRADE_ACTIONS,
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_MOBILE_ACTION_BAR,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledElements: [
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.SIDEBAR_MOBILE_TAP_AREA,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BACKUP_ALERT,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_TABLET_MENU_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BALANCE,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BITCOIN,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS_MOBILE,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_SIDEBAR_BUY,
+ IWalletHTMLElements.BUTTON_SIDEBAR_SELL,
+ IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_BUY,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} .vue-recycle-scroller`,
+ ],
+ };
+
+ return {
+ path: '/',
+ tooltip: {
+ target: IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ content: getOnboardingTexts(OnboardingTourStep.BACKUP_OPTION_FROM_OPTIONS).default,
+ params: {
+ placement: 'right',
+ modifiers: [
+ {
+ name: 'offset',
+ options: {
+ offset: [0, 32],
+ },
+ },
+ ...defaultTooltipModifiers.filter(({ name }) => name !== 'offset'),
+ ],
+ },
+ },
+ ui,
+ } as ITourStep;
+}
diff --git a/src/lib/tour/onboarding/07_AccountOptionsStep.ts b/src/lib/tour/onboarding/07_AccountOptionsStep.ts
new file mode 100644
index 000000000..e5931054a
--- /dev/null
+++ b/src/lib/tour/onboarding/07_AccountOptionsStep.ts
@@ -0,0 +1,79 @@
+import { defaultTooltipModifiers, IWalletHTMLElements } from '..';
+import { OnboardingTourStep, ITourStep, IOnboardingGetStepFnArgs } from '../types';
+import { getOnboardingTexts } from './OnboardingTourTexts';
+
+export function getAccountOptionsStep({ isSmallScreen, isLargeScreen }: IOnboardingGetStepFnArgs): ITourStep {
+ const ui: ITourStep['ui'] = {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_TRADE_ACTIONS,
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_MOBILE_ACTION_BAR,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledElements: [
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.SIDEBAR_MOBILE_TAP_AREA,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BACKUP_ALERT,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_TABLET_MENU_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BALANCE,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BITCOIN,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS_MOBILE,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ IWalletHTMLElements.MODAL_CONTAINER,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_SIDEBAR_BUY,
+ IWalletHTMLElements.BUTTON_SIDEBAR_SELL,
+ IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_BUY,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} .vue-recycle-scroller`,
+ ],
+ };
+
+ return {
+ get path() {
+ return isLargeScreen.value ? '/accounts' : '/accounts?sidebar=true';
+ },
+ tooltip: {
+ get target() {
+ return isSmallScreen.value ? IWalletHTMLElements.MODAL_ACCOUNT_PICTURE : IWalletHTMLElements.MODAL_PAGE;
+ },
+ content: getOnboardingTexts(OnboardingTourStep.ACCOUNT_OPTIONS).default,
+ params: {
+ get placement() {
+ return isSmallScreen.value ? 'bottom-start' : 'right';
+ },
+ get modifiers() {
+ return [
+ {
+ name: 'offset',
+ options: {
+ offset: [isSmallScreen.value ? -13.5 : 0, 16],
+ },
+ },
+ {
+ name: 'preventOverflow',
+ options: {
+ altAxis: true,
+ padding: 8,
+ },
+ },
+ ...defaultTooltipModifiers.filter(({ name }) => !['offset', 'preventOverflow'].includes(name)),
+ ];
+ },
+ },
+ },
+ ui,
+ } as ITourStep;
+}
diff --git a/src/lib/tour/onboarding/08_OnboardingCompleted.ts b/src/lib/tour/onboarding/08_OnboardingCompleted.ts
new file mode 100644
index 000000000..4ada0d978
--- /dev/null
+++ b/src/lib/tour/onboarding/08_OnboardingCompleted.ts
@@ -0,0 +1,93 @@
+import { defaultTooltipModifiers } from '..';
+import { IOnboardingGetStepFnArgs, OnboardingTourStep, ITourStep, IWalletHTMLElements } from '../types';
+import { getOnboardingTexts } from './OnboardingTourTexts';
+
+export function getOnboardingCompletedStep(
+ { root, isLargeScreen }: IOnboardingGetStepFnArgs): ITourStep {
+ const ui: ITourStep['ui'] = {
+ fadedElements: [
+ IWalletHTMLElements.SIDEBAR_TESTNET,
+ IWalletHTMLElements.SIDEBAR_LOGO,
+ IWalletHTMLElements.SIDEBAR_ANNOUNCMENT_BOX,
+ IWalletHTMLElements.SIDEBAR_PRICE_CHARTS,
+ IWalletHTMLElements.SIDEBAR_TRADE_ACTIONS,
+ IWalletHTMLElements.SIDEBAR_ACCOUNT_MENU,
+ IWalletHTMLElements.SIDEBAR_SETTINGS,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_MOBILE_ACTION_BAR,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledElements: [
+ IWalletHTMLElements.SIDEBAR_NETWORK,
+ IWalletHTMLElements.SIDEBAR_MOBILE_TAP_AREA,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BACKUP_ALERT,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_TABLET_MENU_BAR,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BALANCE,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_BITCOIN,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS_MOBILE,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIVE_ADDRESS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_ACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS,
+ IWalletHTMLElements.ADDRESS_OVERVIEW_MOBILE_ACTION_BAR,
+ ],
+ disabledButtons: [
+ IWalletHTMLElements.BUTTON_SIDEBAR_BUY,
+ IWalletHTMLElements.BUTTON_SIDEBAR_SELL,
+ IWalletHTMLElements.BUTTON_ADDRESS_OVERVIEW_BUY,
+ ],
+ scrollLockedElements: [
+ IWalletHTMLElements.ACCOUNT_OVERVIEW_ADDRESS_LIST,
+ `${IWalletHTMLElements.ADDRESS_OVERVIEW_TRANSACTIONS} .vue-recycle-scroller`,
+ ],
+ };
+
+ return {
+ get path() {
+ return isLargeScreen.value ? '/' : '/?sidebar=true';
+ },
+ tooltip: {
+ get target() {
+ return `${IWalletHTMLElements.SIDEBAR_NETWORK} ${isLargeScreen.value ? 'span' : '.consensus-icon'}`;
+ },
+ content: getOnboardingTexts(OnboardingTourStep.ONBOARDING_COMPLETED).default,
+ params: {
+ get placement() {
+ return isLargeScreen.value ? 'right' : 'top-start';
+ },
+ get modifiers() {
+ return [
+ {
+ name: 'preventOverflow',
+ options: {
+ altAxis: false,
+ padding: 8,
+ },
+ },
+ {
+ name: 'offset',
+ options: {
+ offset: [-20, 16],
+ },
+ },
+ ...defaultTooltipModifiers.filter(({ name }) => !['offset', 'preventOverflow'].includes(name)),
+ ];
+ },
+ },
+ button: {
+ text: root.$t('Go to Network'),
+ fn: async (endTour) => {
+ if (endTour) {
+ await endTour();
+ }
+ root.$router.push({
+ name: 'network',
+ params: {
+ showNetworkInfo: 'true',
+ },
+ });
+ },
+ },
+ },
+ ui,
+ } as ITourStep;
+}
diff --git a/src/lib/tour/onboarding/OnboardingTourTexts.ts b/src/lib/tour/onboarding/OnboardingTourTexts.ts
new file mode 100644
index 000000000..f17efca3e
--- /dev/null
+++ b/src/lib/tour/onboarding/OnboardingTourTexts.ts
@@ -0,0 +1,85 @@
+import { ITourStepTexts, OnboardingTourStep } from '../types';
+
+// This is used to trick the translation extraction script into extracting those strings
+const $t = (s: string) => s;
+
+const texts: ITourStepTexts = {
+ [OnboardingTourStep.FIRST_ADDRESS]: {
+ default: [
+ $t('This is your first address, represented by your avatar.'),
+ $t('You can click on the address to copy and share it.'),
+ ],
+ alternative: [
+ $t('This is your first address, represented by your avatar.'),
+ $t('Tap it to open the address details.'),
+ ],
+ },
+ [OnboardingTourStep.TRANSACTION_LIST]: {
+ default: [
+ $t('This is where all your transactions will appear.'),
+ $t('Click the green button to receive a free NIM from Team Nimiq.'),
+ ],
+ alternative: [$t('This is where all your transactions will appear.')],
+ },
+ [OnboardingTourStep.FIRST_TRANSACTION]: {
+ // If user has 1 tx
+ default: [
+ $t('Here’s your first transaction with your first NIM.'),
+ $t('Every NIM address comes with an avatar. They help to make sure you got the right one.'),
+ ],
+ // If user has more than 1 tx
+ alternative: [
+ $t('Here are your transactions.'),
+ $t('Every NIM address comes with an avatar. They help to make sure you got the right one.'),
+ ],
+ },
+ [OnboardingTourStep.BITCOIN_ADDRESS]: {
+ default: [
+ $t('This is your Bitcoin wallet. You get one with every Nimiq account.'),
+ ],
+ },
+ [OnboardingTourStep.WALLET_BALANCE]: {
+ default: [
+ $t('Check the bar-chart to see how your addresses compose your total balance.'),
+ $t('Currently you have 100% NIM, and no BTC.'),
+ ],
+ alternative: [
+ $t('Check the bar-chart to see how your addresses compose your total balance.'),
+ ],
+ },
+ [OnboardingTourStep.BACKUP_ALERT]: {
+ default: [
+ $t('There is no ‘forgot password’. Create a backup to make sure you stay in control.'),
+ ],
+ alternative: [
+ $t('Seriously! There is no ‘forgot password’! Create a backup to make sure you stay in control.'),
+ ],
+ },
+ [OnboardingTourStep.MENU_ICON]: {
+ default: [
+ $t('Tap on the menu icon to access your account and wallet settings.'),
+ ],
+ },
+ [OnboardingTourStep.ACCOUNT_OPTIONS]: {
+ default: [
+ $t('Create, switch and log out of accounts.'),
+ $t('Security options and backup can be found here.'),
+ ],
+ },
+ [OnboardingTourStep.BACKUP_OPTION_FROM_OPTIONS]: {
+ default: [
+ $t('You can always create a new backup. Simply click your account and select ‘Create backup’.'),
+ ],
+ },
+ [OnboardingTourStep.ONBOARDING_COMPLETED]: {
+ default: [
+ $t('Wallet tour completed!'),
+ $t('Nimiq is not just any crypto - Click on {network_icon} Network '
+ + 'and discover true decentralization.'),
+ ],
+ },
+};
+
+export function getOnboardingTexts(i: OnboardingTourStep) {
+ return texts[i];
+}
diff --git a/src/lib/tour/onboarding/index.ts b/src/lib/tour/onboarding/index.ts
new file mode 100644
index 000000000..b51b47f64
--- /dev/null
+++ b/src/lib/tour/onboarding/index.ts
@@ -0,0 +1,73 @@
+import { ScreenTypes } from '@/composables/useWindowSize';
+import { AccountType, useAccountStore } from '@/stores/Account';
+import { computed, SetupContext } from '@vue/composition-api';
+import { ITourOrigin, searchComponentByName } from '..';
+import { IOnboardingGetStepFnArgs, ITourSteps, OnboardingTourStep } from '../types';
+import { getFirstAddressStep } from './01_FirstAddressStep';
+import { getTransactionListStep } from './02_TransactionListStep';
+import { getFirstTransactionStep } from './03_FirstTransactionStep';
+import { getBitcoinAddressStep } from './04_BitcoinAddressStep';
+import { getWalletBalanceStep } from './05_WalletBalanceStep';
+import { getBackupAlertStep } from './06_0_BackupAlertStep';
+import { getMenuIconStep } from './06_1_MenuIconStep';
+import { getBackupOptionNotLargeScreenStep } from './07_1_BackupOptionNotLargeScreenStep';
+import { getBackupOptionLargeScreenStep } from './07_2_BackupOptionLargeScreenStep';
+import { getAccountOptionsStep } from './07_AccountOptionsStep';
+import { getOnboardingCompletedStep } from './08_OnboardingCompleted';
+
+const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
+
+export function getOnboardingTourSteps({ root }: SetupContext, screenTypes: ScreenTypes)
+ : ITourSteps {
+ const toggleHighlightButton = (element: string, highlight: boolean, color: 'gray' | 'orange' | 'green') => {
+ const receiveNim = document
+ .querySelector(element) as HTMLButtonElement;
+ if (!receiveNim) return;
+ receiveNim.classList[highlight ? 'add' : 'remove'](`${color}-highlight`);
+ };
+
+ // Returns the length of the transaction list for the current active account and active address
+ // Easier to access component transaction-list which has already been mounted and has the list
+ // of valid transactions for the current active account and active address
+ const txsLen = computed(() => (searchComponentByName(root, 'transactions-list') as any).txCount || 0);
+
+ const { state, activeAccountInfo } = useAccountStore();
+ const { startedFrom } = (state.tour as { startedFrom: ITourOrigin });
+ const accountIsSecured = computed(() =>
+ activeAccountInfo.value?.type === AccountType.BIP39 && activeAccountInfo.value?.wordsExported);
+
+ const args: IOnboardingGetStepFnArgs = {
+ sleep,
+ root,
+ startedFrom,
+ toggleHighlightButton,
+ txsLen,
+ ...screenTypes,
+ };
+
+ return {
+ [OnboardingTourStep.FIRST_ADDRESS]: getFirstAddressStep(args),
+ [OnboardingTourStep.TRANSACTION_LIST]: getTransactionListStep(args),
+ [OnboardingTourStep.FIRST_TRANSACTION]: getFirstTransactionStep(args),
+ [OnboardingTourStep.BITCOIN_ADDRESS]: getBitcoinAddressStep(args),
+ [OnboardingTourStep.WALLET_BALANCE]: getWalletBalanceStep(args),
+ get [OnboardingTourStep.BACKUP_ALERT]() {
+ // if the user has already backed up their account, skip this step
+ return !accountIsSecured.value ? getBackupAlertStep(args) : undefined;
+ },
+ get [OnboardingTourStep.MENU_ICON]() {
+ // show only this step if it is a new user and is not in a large screen
+ return (!screenTypes.isLargeScreen.value && startedFrom === ITourOrigin.WELCOME_MODAL)
+ ? getMenuIconStep(args) : undefined;
+ },
+ [OnboardingTourStep.ACCOUNT_OPTIONS]: getAccountOptionsStep(args),
+ get [OnboardingTourStep.BACKUP_OPTION_FROM_OPTIONS]() {
+ // if the user has already backed up their account, remind the user that he can backup anytime
+ if (!accountIsSecured.value) return undefined;
+ return (screenTypes.isLargeScreen.value)
+ ? getBackupOptionLargeScreenStep()
+ : getBackupOptionNotLargeScreenStep(args);
+ },
+ [OnboardingTourStep.ONBOARDING_COMPLETED]: getOnboardingCompletedStep(args),
+ };
+}
diff --git a/src/lib/tour/types.ts b/src/lib/tour/types.ts
new file mode 100644
index 000000000..40cf27870
--- /dev/null
+++ b/src/lib/tour/types.ts
@@ -0,0 +1,225 @@
+import { ScreenTypes } from '@/composables/useWindowSize';
+import { Ref, SetupContext } from '@vue/composition-api';
+import { NodeHexagon } from '../NetworkMap';
+
+export enum TourName { ONBOARDING = 'onboarding', NETWORK = 'network' }
+
+export enum OnboardingTourStep {
+ FIRST_ADDRESS,
+ TRANSACTION_LIST,
+ FIRST_TRANSACTION,
+ BITCOIN_ADDRESS,
+ WALLET_BALANCE,
+ BACKUP_ALERT,
+ MENU_ICON,
+ BACKUP_OPTION_FROM_OPTIONS,
+ ACCOUNT_OPTIONS,
+ ONBOARDING_COMPLETED
+}
+
+export enum NetworkTourStep {
+ YOUR_LOCATION,
+ BACKBONE_NODE,
+ METRICS,
+ NETWORK_COMPLETED
+}
+
+export type TourStepIndex = OnboardingTourStep | NetworkTourStep;
+
+// Tooltip placement
+// eslint-disable-next-line max-len
+// https://github.com/floating-ui/floating-ui/blob/59d15d4f81a2d71d9b42d836e47d7114bc32f7f2/packages/core/src/types.ts#L4
+type Alignment = 'start' | 'end';
+type BasePlacement = 'top' | 'right' | 'bottom' | 'left';
+type AlignedPlacement = `${BasePlacement}-${Alignment}`;
+export type Placement = BasePlacement | AlignedPlacement;
+
+export interface ITooltipModifier {
+ name: 'preventOverflow' | 'offset' | 'arrow';
+ options: any;
+}
+
+export interface FutureStepArgs {
+ withDelay: boolean;
+}
+
+export interface ILifecycleArgs {
+ goToNextStep: (args?: FutureStepArgs) => void;
+ goToPrevStep: (args?: FutureStepArgs) => void;
+ goingForward: boolean;
+ ending: boolean;
+ isNextStepDisabled: Ref;
+}
+
+export type IUnmountedFn = ((args: Omit) => Promise | void);
+
+// This interface is the main interface for a step in the tour. It consists of:
+// - path - the route that the step should be shown on
+// - tooltip - the tooltip content and configuration for the step. It is using popperJS as dependency
+// - ui - the UI elements that should be faded out and disabled during the step along with other UI config
+// - lifecycle - the lifecycle hooks for the step. Read more at the top of the file of Tour.vue file
+export interface ITourStep {
+ path: `/${'' | 'transactions' | 'accounts' | 'network'}${'' | '?sidebar=true'}`;
+
+ // data for the steps of v-tour
+ tooltip: {
+
+ // selector for the element that the tooltip should be attached to
+ target: string,
+
+ // If the content is an array of string it will be displayed as a list
+ // otherwise it will be displayed as a paragraph.
+ // Some lines can use a string from type IContentSpecialItem
+ content: (string | string[] | IContentSpecialItem)[],
+
+ // This configuration is the same as the one used in popperJS
+ params: {
+ placement: BasePlacement | AlignedPlacement,
+ modifiers?: ITooltipModifier[] | void,
+ },
+
+ // Some steps required a custom button in the bottom of the tooltip
+ button?: {
+ text: string,
+ fn: (callback?: () => Promise) => void,
+ },
+ };
+
+ // function to call when the step is changed. Read more at Tour.vue file
+ lifecycle?: {
+ created?: (args: Omit) => Promise | void,
+ mounted?: (args: ILifecycleArgs) => IUnmountedFn | Promise | void,
+ };
+
+ ui: {
+ // Elements that must have opacity to focus attention in other elements in screen
+ fadedElements?: string[], // array of selectors
+
+ // Elements that shouldn't allow interactivity
+ disabledElements?: string[], // array of selectors
+
+ // Allow user to go to the next step using buttons from the tour
+ // If false, user is required to make a click in a button which is not part of the tour
+ isNextStepDisabled?: boolean,
+
+ // Buttons with primary classes are disabled
+ disabledButtons?: string[],
+
+ // Elements that cannot be scrolled. See more at Tour.vue@_onScrollInLockedElement
+ scrollLockedElements?: string[],
+
+ // Elements that are expected to be clicked by the user. If they are clicked, the tour
+ // will ignore them and will not make the flash animation
+ explicitInteractableElements?: string[],
+ };
+}
+
+// x will be the index of the step in the tour
+// JSON objects have strings as keys normally, but JS also can work with
+// numbers as keys which is more convinient for us in this case
+export type ITourSteps = {
+ [x in T]?: ITourStep;
+};
+
+// From which part the user can start the tour
+export enum ITourOrigin {
+ SETTINGS = 'settings',
+ WELCOME_MODAL = 'welcome-modal',
+}
+
+export type ITourStepTexts = {
+ [x in T]: {
+ default: (string | string[])[], alternative?: string[],
+ }
+}
+
+// Every step is defined in its own file as a function. The function receives this interface as a parameter
+export type IOnboardingGetStepFnArgs = ScreenTypes & {
+ root: SetupContext['root'],
+ sleep: (ms: number) => Promise,
+ startedFrom: ITourOrigin,
+ toggleHighlightButton: (selector: string, highlight: boolean, color: 'gray' | 'orange' | 'green') => void,
+ txsLen: Readonly][>,
+};
+
+// Every step is defined in its own file as a function. The function receives this interface as a parameter
+export type INetworkGetStepFnArgs = ScreenTypes & {
+ root: SetupContext['root'],
+ nodes: () => NodeHexagon[], // nodes on the map
+ selfNodeIndex: number,
+ scrollIntoView: (x: number) => void,
+ sleep: (ms: number) => Promise,
+}
+
+// Events that will be used to communicate between LargeScreenTourManager.vue and Tour.vue
+export type ITourBroadcast = ITourBroadcastEnd | ITourBroadcastStepChanged | ITourBroadcastClickedOutsideTour
+
+interface ITourBroadcastEnd {
+ type: 'end-tour';
+}
+
+interface ITourBroadcastClickedOutsideTour {
+ type: 'clicked-outside-tour';
+}
+
+export interface ITourBroadcastStepChanged {
+ type: 'tour-step-changed';
+ payload: {
+ currentStep: TourStepIndex,
+ nSteps: number,
+ };
+}
+
+// Some steps contain texts with special requirements
+export enum IContentSpecialItem {
+ HR = '{HR}',
+ ICON_NETWORK_WORLD = '{network_icon}',
+ ICON_ACCOUNT = '{account_icon}',
+ BACK_TO_ADDRESSES = '{back_to_addresses}',
+}
+
+// Elements that are need to be opacified or non-interactive at some point during the tour
+export enum IWalletHTMLElements {
+ SIDEBAR_TESTNET = '.sidebar .testnet-notice',
+ SIDEBAR_LOGO = '.sidebar .logo',
+ SIDEBAR_ANNOUNCMENT_BOX = '.sidebar .announcement-box',
+ SIDEBAR_PRICE_CHARTS = '.sidebar .price-chart-wrapper',
+ SIDEBAR_TRADE_ACTIONS = '.sidebar .trade-actions',
+ SIDEBAR_ACCOUNT_MENU = '.sidebar .account-menu',
+ SIDEBAR_NETWORK = '.sidebar .network',
+ SIDEBAR_SETTINGS = '.sidebar .settings',
+ SIDEBAR_MOBILE_TAP_AREA = '.mobile-tap-area',
+
+ ACCOUNT_OVERVIEW_BACKUP_ALERT = '.account-overview .backup-warning',
+ ACCOUNT_OVERVIEW_TABLET_MENU_BAR = '.account-overview .mobile-menu-bar',
+ ACCOUNT_OVERVIEW_BALANCE = '.account-overview .account-balance-container',
+ ACCOUNT_OVERVIEW_ADDRESS_LIST = '.account-overview .address-list',
+ ACCOUNT_OVERVIEW_BITCOIN = '.account-overview .bitcoin-account',
+ ACCOUNT_OVERVIEW_MOBILE_ACTION_BAR = '.account-overview .mobile-action-bar',
+
+ ADDRESS_OVERVIEW_ACTIONS_MOBILE = '.address-overview .actions-mobile',
+ ADDRESS_OVERVIEW_ACTIVE_ADDRESS = '.address-overview .active-address',
+ ADDRESS_OVERVIEW_ACTIONS = '.address-overview .actions',
+ ADDRESS_OVERVIEW_TRANSACTIONS = '.address-overview .transaction-list',
+ ADDRESS_OVERVIEW_MOBILE_ACTION_BAR = '.address-overview .mobile-action-bar',
+
+ BUTTON_SIDEBAR_BUY = '.sidebar .trade-actions button:nth-child(1)',
+ BUTTON_SIDEBAR_SELL = '.sidebar .trade-actions button:nth-child(2)',
+ BUTTON_ADDRESS_OVERVIEW_BUY = '.address-overview .transaction-list .after-first-tx button',
+ BUTTON_ADDRESS_OVERVIEW_RECEIVE_FREE_NIM = // The comma in document.querySelector works like an OR gate
+ '.address-overview .transaction-list .empty-state a,' // Mainnet uses an a element
+ + '.address-overview .transaction-list .empty-state button', // Devnet uses a button element
+ BUTTON_ADDRESS_BACKUP_ALERT = '.account-overview .backup-warning button',
+
+ MODAL_CONTAINER = '.modal.backdrop',
+ MODAL_WRAPPER = '.modal .wrapper',
+ MODAL_PAGE = '.modal .small-page',
+ MODAL_ACCOUNT_PICTURE = '.modal .small-page .account-menu-item .icon svg',
+ MODAL_CLOSE_BUTTON = '.modal .close-button',
+
+ NETWORK_STATS = '.network .network-stats',
+ NETWORK_MAP = '.network .network-map',
+ NETWORK_NODES = '.network .network-map .nodes',
+ NETWORK_SCROLLER = '.network .scroller',
+ NETWORK_TABLET_MENU_BAR = '.network .menu-bar',
+}
diff --git a/src/router.ts b/src/router.ts
index c76359ace..9191e7338 100644
--- a/src/router.ts
+++ b/src/router.ts
@@ -16,6 +16,9 @@ const Network = () =>
// Modals
const AccountMenuModal = () =>
import(/* webpackChunkName: "account-menu-modal" */ './components/modals/AccountMenuModal.vue');
+const WelcomeModal = () =>
+ import(/* webpackChunkName: "discover-the-nimiq-wallet-modal" */
+ './components/modals/WelcomeModal.vue');
const SendModal = () => import(/* webpackChunkName: "send-modal" */ './components/modals/SendModal.vue');
const ReceiveModal = () => import(/* webpackChunkName: "receive-modal" */ './components/modals/ReceiveModal.vue');
const AddressSelectorModal = () =>
@@ -26,8 +29,6 @@ const TradeModal = () => import(/* webpackChunkName: "trade-modal" */ './compone
const BuyOptionsModal = () =>
import(/* webpackChunkName: "buy-options-modal" */ './components/modals/BuyOptionsModal.vue');
const ScanQrModal = () => import(/* webpackChunkName: "scan-qr-modal" */ './components/modals/ScanQrModal.vue');
-const WelcomeModal = () =>
- import(/* webpackChunkName: "welcome-modal" */ './components/modals/WelcomeModal.vue');
const MigrationWelcomeModal = () =>
import(/* webpackChunkName: "migration-welcome-modal" */ './components/modals/MigrationWelcomeModal.vue');
const DisclaimerModal = () =>
diff --git a/src/stores/Account.ts b/src/stores/Account.ts
index 7fa9ffd91..116d00c54 100644
--- a/src/stores/Account.ts
+++ b/src/stores/Account.ts
@@ -1,12 +1,17 @@
-import { createStore } from 'pinia';
+import { ITourOrigin, TourName, TourStepIndex } from '@/lib/tour';
import { Account } from '@nimiq/hub-api';
-import { useAddressStore } from './Address';
+import { createStore } from 'pinia';
import { CryptoCurrency } from '../lib/Constants';
+import { useAddressStore } from './Address';
export type AccountState = {
accountInfos: {[id: string]: AccountInfo},
activeAccountId: string | null,
activeCurrency: CryptoCurrency,
+ tour:
+ { name: TourName.NETWORK, step: TourStepIndex } |
+ { name: TourName.ONBOARDING, step: TourStepIndex, startedFrom: ITourOrigin } |
+ null,
}
// Mirror of Hub WalletType, which is not exported
@@ -29,6 +34,7 @@ export const useAccountStore = createStore({
accountInfos: {},
activeAccountId: null,
activeCurrency: CryptoCurrency.NIM,
+ tour: null,
} as AccountState),
getters: {
accountInfos: (state) => state.accountInfos,
@@ -105,5 +111,8 @@ export const useAccountStore = createStore({
setActiveCurrency(currency: CryptoCurrency) {
this.state.activeCurrency = currency;
},
+ setTour(tour: AccountState['tour']) {
+ this.state.tour = tour;
+ },
},
});
diff --git a/yarn.lock b/yarn.lock
index e50c34a4b..0a6a90fcd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1253,6 +1253,11 @@
"@nodelib/fs.scandir" "2.1.4"
fastq "^1.6.0"
+"@popperjs/core@^2.9.1":
+ version "2.11.2"
+ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9"
+ integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==
+
"@sentry/browser@6.14.3":
version "6.14.3"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.14.3.tgz#4e3b67a48b12a70c381cab326d053ee5dfc087d6"
@@ -6868,6 +6873,11 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.10.0"
+jump.js@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/jump.js/-/jump.js-1.0.2.tgz#e0641b47f40a38f2139c25fda0500bf28e43015a"
+ integrity sha1-4GQbR/QKOPITnCX9oFAL8o5DAVo=
+
killable@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@@ -10842,6 +10852,16 @@ vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
+vue-tour@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/vue-tour/-/vue-tour-2.0.0.tgz#aa4489cfc9f9090ca57d3208a074010f3be8ec05"
+ integrity sha512-vhKzqdhunQ3EoO1733UxhOB389u3EKv2X8JqYhX4tIq4ilqlZtnY3azPFBYPFmnAqHn5RyZBrP2CpqSaxTs8og==
+ dependencies:
+ "@popperjs/core" "^2.9.1"
+ hash-sum "^2.0.0"
+ jump.js "^1.0.2"
+ vue "^2.6.12"
+
"vue-virtual-scroller@https://github.com/sisou/vue-virtual-scroller#nimiq/build":
version "1.0.10"
resolved "https://github.com/sisou/vue-virtual-scroller#8e03a1432c611856e92a3f61f496b93337338701"
@@ -10850,7 +10870,7 @@ vue-template-es2015-compiler@^1.9.0:
vue-observe-visibility "^0.4.4"
vue-resize "^0.4.5"
-vue@^2.6.11:
+vue@^2.6.11, vue@^2.6.12:
version "2.6.14"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==
]