Skip to content

Commit 734d254

Browse files
committed
wip
1 parent 45c035f commit 734d254

File tree

9 files changed

+991
-36
lines changed

9 files changed

+991
-36
lines changed

src/components/layouts/Sidebar.vue

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,7 @@
3535

3636
<div class="trade-actions" v-if="!isLegacyAccount">
3737
<Tooltip
38-
v-if="(
39-
$config.fastspot.enabled
40-
&& fastspotEnabledCryptoSwapAssets.length
41-
&& fastspotEnabledFiatSwapAssets.length
42-
)
43-
|| $config.moonpay.enabled
44-
|| $config.simplex.enabled"
38+
v-if="canBuy"
4539
preferredPosition="top right"
4640
:container="$parent"
4741
theme="inverse"
@@ -63,8 +57,12 @@
6357
}}</template>
6458
</Tooltip>
6559

60+
<button v-if="isDemoEnabled" class="nq-button-s inverse"
61+
@click="openModal('demo-sell', { direction: 'sell' })" @mousedown.prevent="hideTooltips">
62+
{{ $t('Sell') }}
63+
</button>
6664
<Tooltip
67-
v-if="/* (fastspotEnabledCryptoSwapAssets.length && fastspotEnabledFiatSwapAssets.length)
65+
v-else-if="/* (fastspotEnabledCryptoSwapAssets.length && fastspotEnabledFiatSwapAssets.length)
6866
|| */ $config.moonpay.enabled"
6967
preferredPosition="top right"
7068
:container="$parent"
@@ -203,6 +201,7 @@ import AttentionDot from '../AttentionDot.vue';
203201
import { useAddressStore } from '../../stores/Address';
204202
import { useBtcAddressStore } from '../../stores/BtcAddress';
205203
import { usePolygonAddressStore } from '../../stores/PolygonAddress';
204+
import { useDemoStore } from '../../stores/Demo';
206205
import { useSettingsStore } from '../../stores/Settings';
207206
import { useAccountStore, AccountType } from '../../stores/Account';
208207
import { useAccountSettingsStore } from '../../stores/AccountSettings';
@@ -328,6 +327,14 @@ export default defineComponent({
328327
: null;
329328
});
330329
330+
const { isDemoEnabled } = useDemoStore();
331+
332+
const canBuy = computed(() => (
333+
config.fastspot.enabled
334+
&& fastspotEnabledCryptoSwapAssets.value.length && fastspotEnabledFiatSwapAssets.value.length)
335+
|| config.moonpay.enabled || config.simplex.enabled || isDemoEnabled,
336+
);
337+
331338
return {
332339
CryptoCurrency,
333340
navigateTo,
@@ -338,6 +345,8 @@ export default defineComponent({
338345
priceChartTimeRange,
339346
switchPriceChartTimeRange,
340347
isLegacyAccount,
348+
canBuy,
349+
isDemoEnabled,
341350
walletActivatedCurrencies,
342351
fastspotEnabledFiatSwapAssets,
343352
fastspotEnabledCryptoSwapAssets,

src/components/modals/BuyOptionsModal.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@
204204
<script lang="ts">
205205
import { computed, defineComponent, onMounted, ref } from '@vue/composition-api';
206206
import { PageBody, FiatAmount, CircleSpinner } from '@nimiq/vue-components';
207+
import { useDemoStore } from '@/stores/Demo';
207208
import Modal from './Modal.vue';
208209
import CountrySelector from '../CountrySelector.vue';
209210
import CountryFlag from '../CountryFlag.vue';
@@ -234,13 +235,16 @@ export default defineComponent({
234235
235236
const country = ref<Country>(null);
236237
238+
const { isDemoEnabled } = useDemoStore();
237239
const isMoonpayAvailable = computed(() => { // eslint-disable-line arrow-body-style
240+
if (isDemoEnabled.value) return true;
238241
if (!config.moonpay.enabled) return false;
239242
if (!country.value) return true;
240243
return MOONPAY_COUNTRY_CODES.includes(country.value.code);
241244
});
242245
243246
const isSimplexAvailable = computed(() => { // eslint-disable-line arrow-body-style
247+
if (isDemoEnabled.value) return true;
244248
if (!config.simplex.enabled) return false;
245249
if (!country.value) return true;
246250
return SIMPLEX_COUNTRY_CODES.includes(country.value.code);

src/components/modals/Modal.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ const Modal = defineComponent({
8787
8888
while (context.root.$route.matched.find((routeRecord) => 'modal' in routeRecord.components
8989
|| 'persistent-modal' in routeRecord.components
90-
|| Object.values(routeRecord.components).some((component) => /modal/i.test(component.name || '')))
90+
|| Object.values(routeRecord.components).some((component) => /modal/i.test(
91+
'name' in component ? component.name as string : '')))
9192
) {
9293
context.root.$router.back();
9394
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<template>
2+
<Modal :showOverlay="showOverlay">
3+
<PageHeader class="flex-column">
4+
<h1 class="nq-h1" v-if="direction === 'buy'">{{ $t('Buy NIM') }}</h1>
5+
<h1 class="nq-h1" v-else>{{ $t('Sell NIM') }}</h1>
6+
<div class="demo-warning nq-label">
7+
{{ $t('DEMO') }}
8+
</div>
9+
</PageHeader>
10+
<PageBody>
11+
<div class="flex-row">
12+
13+
<AmountInput v-model="amount" :decimals="5">
14+
<AmountMenu slot="suffix" class="ticker" currency="nim" :open="amountMenuOpened"
15+
:activeCurrency="activeCurrency" :fiatCurrency="fiatCurrency" :feeOption="false"
16+
:otherFiatCurrencies="otherFiatCurrencies"
17+
@click.native.stop="amountMenuOpened = !amountMenuOpened" :sendAllOption="direction === 'sell'"
18+
/>
19+
</AmountInput>
20+
</div>
21+
</PageBody>
22+
<PageFooter>
23+
<button class="nq-button light-blue" @click="buyDummyNim" :disabled="!amount">
24+
<template v-if="direction === 'buy'">{{ $t('Buy NIM') }}</template>
25+
<template v-else>{{ $t('Sell NIM') }}</template>
26+
</button>
27+
</PageFooter>
28+
29+
<PageBody slot="overlay" class="overlay-content">
30+
<HighFiveIcon />
31+
<h2 class="nq-h2">
32+
<template v-if="direction === 'buy'">{{ $t('Your NIM is under its way!') }}</template>
33+
<template v-else>{{ $t('Your NIM has been sold!') }}</template>
34+
</h2>
35+
<p>
36+
{{ $t('This transaction is instant and secure.') }}
37+
</p>
38+
</PageBody>
39+
</Modal>
40+
</template>
41+
42+
<script lang="ts">
43+
import { computed, defineComponent, ref } from '@vue/composition-api';
44+
import { PageBody, PageHeader, PageFooter } from '@nimiq/vue-components';
45+
import AmountInput from '@/components/AmountInput.vue';
46+
import AmountMenu from '@/components/AmountMenu.vue';
47+
import Modal from '@/components/modals/Modal.vue';
48+
import { useAccountStore } from '@/stores/Account';
49+
import { useFiatStore } from '@/stores/Fiat';
50+
import { FIAT_CURRENCIES_OFFERED } from '@/lib/Constants';
51+
// import { useTransactionsStore } from '@/stores/Transactions';
52+
import { useDemoStore } from '@/stores/Demo';
53+
import { useRouter } from '@/router';
54+
import HighFiveIcon from '@/components/icons/HighFiveIcon.vue';
55+
import { useAddressStore } from '@/stores/Address';
56+
57+
export default defineComponent({
58+
props: {
59+
direction: String,
60+
},
61+
setup() {
62+
const { activeCurrency } = useAccountStore();
63+
const { currency: fiatCurrency } = useFiatStore();
64+
const { activeAddressInfo } = useAddressStore();
65+
const otherFiatCurrencies = computed(() =>
66+
FIAT_CURRENCIES_OFFERED.filter((fiat) => fiat !== fiatCurrency.value));
67+
const amount = ref(0);
68+
const amountMenuOpened = ref(false);
69+
const showOverlay = ref(false);
70+
const router = useRouter();
71+
72+
const maxSendableAmount = computed(() => Math.max((activeAddressInfo.value!.balance || 0), 0));
73+
const sendMax = () => amount.value = maxSendableAmount.value;
74+
75+
function buyDummyNim() {
76+
useDemoStore().buyDemoNim(amount.value);
77+
showOverlay.value = true;
78+
setTimeout(() => {
79+
showOverlay.value = false;
80+
router.push('/');
81+
}, 4000);
82+
}
83+
84+
return {
85+
amount,
86+
activeCurrency,
87+
fiatCurrency,
88+
otherFiatCurrencies,
89+
amountMenuOpened,
90+
buyDummyNim,
91+
showOverlay,
92+
sendMax,
93+
};
94+
},
95+
components: {
96+
Modal,
97+
AmountInput,
98+
AmountMenu,
99+
PageHeader,
100+
PageBody,
101+
PageFooter,
102+
HighFiveIcon,
103+
},
104+
});
105+
</script>
106+
107+
<style scoped lang="scss">
108+
.small-page {
109+
> .page-header {
110+
overflow: hidden;
111+
112+
.demo-warning {
113+
margin: 0;
114+
text-align: center;
115+
position: absolute;
116+
top: 0;
117+
left: 0;
118+
right: 0;
119+
background: var(--nimiq-orange-bg);
120+
color: white;
121+
padding: 0.5rem 0;
122+
}
123+
}
124+
}
125+
126+
::v-deep .nq-card.overlay {
127+
background: var(--nimiq-green);
128+
color: white;
129+
130+
.overlay-content {
131+
display: flex;
132+
flex-direction: column;
133+
justify-content: center;
134+
align-items: center;
135+
text-align: center;
136+
svg {
137+
width: 128px;
138+
height: 128px;
139+
}
140+
141+
p {
142+
margin-top: 0;
143+
text-wrap: pretty;
144+
}
145+
}
146+
147+
.close-button {
148+
display: none;
149+
}
150+
}
151+
</style>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<template>
2+
<Modal>
3+
<PageHeader class="flex-column">
4+
<h1 class="nq-h1">{{ $t('Nimiq Demo') }}</h1>
5+
</PageHeader>
6+
<PageBody>
7+
<p class="nq-p">
8+
{{ $t('This is not a real Nimiq Wallet. It is just a demo so it is limited in functionality.') }}
9+
</p>
10+
<p>
11+
{{ $t('You can open a free NIM account in less than a minute.') }}
12+
</p>
13+
</PageBody>
14+
<PageFooter>
15+
<a href="https://wallet.nimiq.com" target="_blank" class="nq-button light-blue">
16+
{{ $t('Open Nimiq Wallet') }}
17+
</a>
18+
</PageFooter>
19+
</Modal>
20+
</template>
21+
22+
<script lang="ts">
23+
import { defineComponent } from '@vue/composition-api';
24+
import { PageBody, PageHeader, PageFooter } from '@nimiq/vue-components';
25+
import Modal from '../Modal.vue';
26+
27+
export default defineComponent({
28+
components: {
29+
Modal,
30+
PageHeader,
31+
PageBody,
32+
PageFooter,
33+
},
34+
});
35+
</script>

src/hub.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { WELCOME_MODAL_LOCALSTORAGE_KEY, WELCOME_STAKING_MODAL_LOCALSTORAGE_KEY
3838
import { usePwaInstallPrompt } from './composables/usePwaInstallPrompt';
3939
import type { SetupSwapWithKycResult, SWAP_KYC_HANDLER_STORAGE_KEY } from './swap-kyc-handler'; // avoid bundling
4040
import type { RelayServerInfo } from './lib/usdc/OpenGSN';
41-
import { HubApiMock, isPlaygroundEnabled } from './stores/Playground';
41+
import { DemoHubApi, checkIfDemoIsActive } from './stores/Demo';
4242

4343
export function shouldUseRedirects(ignoreSettings = false): boolean {
4444
if (!ignoreSettings) {
@@ -115,7 +115,7 @@ function getBehavior({
115115

116116
// We can't use the reactive config via useConfig() here because that one can only be used after the composition-api
117117
// plugin has been registered in Vue 2.
118-
const hubApi = new HubApi(Config.hubEndpoint);
118+
const hubApi = checkIfDemoIsActive() ? DemoHubApi.create() : new HubApi(Config.hubEndpoint);
119119

120120
hubApi.on(HubApi.RequestType.ONBOARD, async (accounts) => {
121121
const { config } = useConfig();

src/main.ts

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { launchElectrum } from './electrum';
1717
import { launchPolygon } from './ethers';
1818
import { useAccountStore } from './stores/Account';
1919
import { useFiatStore } from './stores/Fiat';
20+
import { useDemoStore } from './stores/Demo';
2021
import { useSettingsStore } from './stores/Settings';
2122
import router from './router';
2223
import { i18n, loadLanguage } from './i18n/i18n-setup';
@@ -48,14 +49,21 @@ Vue.use(VuePortal, { name: 'Portal' });
4849

4950
async function start() {
5051
initPwa(); // Must be called as soon as possible to catch early browser events related to PWA
51-
await initStorage(); // Must be awaited before starting Vue
52-
initTrials(); // Must be called after storage was initialized, can affect Config
53-
// Must run after VueCompositionApi has been enabled and after storage was initialized. Could potentially run in
54-
// background and in parallel to syncFromHub, but RedirectRpcClient.init does not actually run async code anyways.
55-
await initHubApi();
56-
syncFromHub(); // Can run parallel to Vue initialization; must be called after storage was initialized.
57-
58-
serviceWorkerHasUpdate.then((hasUpdate) => useSettingsStore().state.updateAvailable = hasUpdate);
52+
const { isDemoEnabled } = useDemoStore();
53+
54+
if (!isDemoEnabled.value) {
55+
await initStorage(); // Must be awaited before starting Vue
56+
initTrials(); // Must be called after storage was initialized, can affect Config
57+
// Must run after VueCompositionApi has been enabled and after storage was initialized. Could potentially run in
58+
// background and in parallel to syncFromHub, but RedirectRpcClient.init does not actually run async code
59+
// anyways.
60+
await initHubApi();
61+
syncFromHub(); // Can run parallel to Vue initialization; must be called after storage was initialized.
62+
63+
serviceWorkerHasUpdate.then((hasUpdate) => useSettingsStore().state.updateAvailable = hasUpdate);
64+
} else {
65+
useDemoStore().initialize(router);
66+
}
5967

6068
// Update exchange rates every 2 minutes or every 10 minutes, depending on whether the Wallet is currently actively
6169
// used. If an update takes longer than that time due to a provider's rate limit, wait until the update succeeds
@@ -94,28 +102,34 @@ async function start() {
94102
const { language } = useSettingsStore();
95103
loadLanguage(language.value);
96104

97-
startSentry();
105+
if (!isDemoEnabled.value) {
106+
startSentry();
107+
}
98108

99109
const { config } = useConfig();
100110

101-
if (config.environment !== ENV_MAIN) {
111+
if (isDemoEnabled.value) {
112+
document.title = 'Nimiq Wallet Demo';
113+
} else if (config.environment !== ENV_MAIN) {
102114
document.title = 'Nimiq Testnet Wallet';
103115
}
104116

105-
watch(() => {
106-
if (!config.fastspot.apiEndpoint || !config.fastspot.apiKey) return;
107-
initFastspotApi(config.fastspot.apiEndpoint, config.fastspot.apiKey);
108-
});
109-
110-
watch(() => {
111-
if (!config.oasis.apiEndpoint) return;
112-
initOasisApi(config.oasis.apiEndpoint);
113-
});
114-
115-
watch(() => {
116-
if (!config.ten31Pass.enabled) return;
117-
initKycConnection();
118-
});
117+
if (!isDemoEnabled.value) {
118+
watch(() => {
119+
if (!config.fastspot.apiEndpoint || !config.fastspot.apiKey) return;
120+
initFastspotApi(config.fastspot.apiEndpoint, config.fastspot.apiKey);
121+
});
122+
123+
watch(() => {
124+
if (!config.oasis.apiEndpoint) return;
125+
initOasisApi(config.oasis.apiEndpoint);
126+
});
127+
128+
watch(() => {
129+
if (!config.ten31Pass.enabled) return;
130+
initKycConnection();
131+
});
132+
}
119133

120134
// Make reactive config accessible in components
121135
Vue.prototype.$config = config;

0 commit comments

Comments
 (0)