Add merchant mode (#556)

* merchant mode (work in progress)

stuff left to do: payment successful state, footer state when a different amount is received with an option to create new qr with the difference (if amount is less than requested) or an option to consider payment as paid (if amount is more than requested, as it might be a different customer)

* fix header not being visually centered

* merchant mode prompts and style improvements

"transaction complete" state still left to do

* merchant mode prompts style tweaks, fix account select alignment

* merchant mode payment complete state

* replace tiny icon with a big button

* unused variable
This commit is contained in:
keeri 2022-06-27 20:04:50 +00:00 committed by GitHub
commit 3bc8d4182d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 625 additions and 10 deletions

View file

@ -1,3 +1,18 @@
.merchant-mode-text-full {
display: none;
}
.merchant-mode-text-short {
display: inline-block;
}
@media (min-width: 460px) {
.merchant-mode-text-full {
display: inline-block;
}
.merchant-mode-text-short {
display: none;
}
}
.label-block {
margin-left: 10px;
margin-bottom: 10px;
@ -63,9 +78,6 @@
max-width: 280px;
margin: 25px auto 0 auto;
}
.copy-address-button {
margin-top: 25px;
}
@media (max-width: 959px) {
.qr-code {
max-width: 250px;
@ -161,4 +173,117 @@
transform: scale(0);
opacity: 1;
}
}
}
.merchant-mode-overlay {
background-color: #FFF;
z-index: 1050; /* above notifications */
padding: 15px !important;
}
.merchant-mode-contents {
box-sizing: border-box;
height: 100%;
padding: 20px;
}
@media (max-width: 699px) {
.merchant-mode-contents {
padding: 10px;
}
}
.merchant-mode-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 25px;
}
@media (max-width: 699px) {
.merchant-mode-header {
flex-direction: column;
padding-left: 0;
}
}
@media (min-width: 1200px) {
.merchant-mode-header {
position: fixed;
width: calc(100% - 86px);
}
}
.merchant-mode-exit .label {
font-size: 1.2em;
}
.merchant-mode-logo {
width: 200px;
height: 85px;
background: url('../../../assets/img/nault-logo.svg');
background-size: contain;
background-repeat: no-repeat;
flex-shrink: 0;
}
.merchant-mode-exit {
cursor: pointer;
padding: 6px 23px 8px 20px;
border: 2px solid transparent;
border-radius: 50px;
transition: border-color 100ms ease-in-out;
}
.merchant-mode-exit:hover {
border-color: #676686AA;
}
.merchant-centered-container {
padding-bottom: 170px;
max-width: 500px;
}
@media (min-width: 1200px) {
.merchant-centered-container {
padding-bottom: 0;
}
}
.merchant-mode-icon-qr-code {
width: 19px;
height: 19px;
margin-right: 10px;
background-color: currentcolor;
-webkit-mask-image: url('assets/img/qr-code.svg');
mask-image: url('assets/img/qr-code.svg');
}
.merchant-mode-qr-code {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 290px;
height: 290px;
border-radius: 20px;
border: 2px solid #19191B;
background-color: #FFF;
}
.merchant-mode-amount-integer,
.merchant-mode-amount-fractional,
.merchant-mode-currency-name {
font-family: 'Montserrat', Arial, Helvetica, sans-serif;
font-size: 34px;
}
.merchant-mode-amount-integer {
font-weight: 700;
}
.merchant-mode-amount-fractional {
font-weight: 500;
}
.merchant-mode-currency-name {
font-weight: 400;
}
.merchant-mode-currency-margin {
margin-left: 10px;
}

View file

@ -1,7 +1,24 @@
<div class="uk-animation-slide-left-small" uk-grid>
<div class="uk-width-1-1">
<ng-template #switchToMerchantModeButton>
<button
class="uk-width-1-1 uk-width-auto@s uk-button uk-button-primary uk-text-center nlt-icon-button nlt-button-green"
type="button"
(click)="merchantModeEnable()"
>
<span class="nlt-icon" uk-icon="icon: cart;"></span>
<span class="merchant-mode-text-full">Switch to Merchant Mode</span>
<span class="merchant-mode-text-short">Merchant Mode</span>
</button>
</ng-template>
<h2 class="uk-heading-divider">Receive Nano</h2>
<div class="uk-margin-bottom uk-flex uk-flex-between">
<h2 class="uk-flex-1 uk-heading-divider uk-margin-remove">Receive Nano</h2>
<div class="uk-flex-none uk-visible@s uk-flex uk-flex-top uk-margin-medium-left">
<ng-container *ngTemplateOutlet="switchToMerchantModeButton"></ng-container>
</div>
</div>
<div class="uk-card uk-card-default uk-margin">
<div class="uk-card-body">
@ -53,14 +70,15 @@
<div style="padding-top: 100%"></div>
</div>
</ng-template>
<div *ngIf="(pendingAccountModel !== '0')">
<div class="uk-flex uk-flex-center uk-flex-middle uk-text-center qr-address">
<div>
<div class="uk-flex uk-flex-center uk-flex-middle uk-text-center qr-address" *ngIf="(pendingAccountModel !== '0')">
<app-nano-account-id [accountID]="pendingAccountModel" middle="break" class="nano-address-monospace uk-width-auto" style="max-width: 90%;"></app-nano-account-id>
<a class="span-icon hide-on-small-viewports" ngxClipboard [cbContent]="pendingAccountModel" (cbOnSuccess)="copied()" uk-icon="icon: copy" title="Copy Account Address" uk-tooltip></a>
</div>
<div class="only-on-small-viewports uk-text-center">
<div class="uk-margin-medium-top only-on-small-viewports nlt-button-group uk-flex uk-flex-column uk-flex-middle">
<button
class="copy-address-button uk-width-1-1 uk-width-4-5@s uk-button uk-button-primary uk-text-center"
*ngIf="(pendingAccountModel !== '0')"
class="uk-width-1-1 uk-width-4-5@s uk-button uk-button-primary uk-text-center nlt-icon-button"
[class.nlt-button-success]="recentlyCopiedAccountAddress"
[class.uk-disabled]="recentlyCopiedAccountAddress"
type="button"
@ -68,8 +86,12 @@
[cbContent]="pendingAccountModel"
(cbOnSuccess)="copiedAccountAddress()"
>
<span class="nlt-icon" uk-icon="icon: copy;"></span>
{{ recentlyCopiedAccountAddress ? 'Copied!' : 'Copy address' }}
</button>
<div class="uk-hidden@s uk-width-1-1 uk-flex">
<ng-container *ngTemplateOutlet="switchToMerchantModeButton"></ng-container>
</div>
</div>
</div>
</div>
@ -281,3 +303,264 @@
</div>
</div>
<div class="merchant-mode-overlay" id="merchant-mode-modal" uk-modal>
<div class="merchant-mode-contents uk-flex uk-flex-column uk-flex-middle">
<div
class="uk-width-1-1 merchant-mode-header"
[class.uk-margin-medium-bottom]="inMerchantModeQR === false"
[class.uk-margin-bottom]="inMerchantModeQR === true"
>
<div class="merchant-mode-logo"></div>
<div
class="merchant-mode-exit uk-flex uk-flex-middle"
[class.uk-visible@s]="(inMerchantModeQR === true)"
(click)="inMerchantModeQR ? merchantModeHideQR() : merchantModeDisable()"
>
<span uk-icon="icon: close; ratio: 1.4;" class="uk-margin-small-right" style="margin-top: 2px;"></span>
<span class="label">{{ inMerchantModeQR ? 'Cancel Payment' : 'Exit Merchant Mode' }}</span>
</div>
</div>
<div class="merchant-centered-container uk-flex-1 uk-width-1-1 uk-flex uk-flex-column uk-flex-center uk-flex-middle uk-text-center">
<ng-container *ngIf="(inMerchantModePaymentComplete === false) && (inMerchantModeQR === false) && (pendingAccountModel === '0')">
<div class="uk-width-1-1 uk-card uk-card-default uk-margin-bottom">
<div class="uk-card-body">
<p class="uk-text-large">Select the <span class="uk-text-primary">destination account</span></p>
<select id="form-horizontal-select" class="uk-select" [(ngModel)]="pendingAccountModel" (change)="onSelectedAccountChange(pendingAccountModel)">
<option [value]="0"></option>
<option *ngFor="let account of accounts" [value]="account.id">{{account.addressBookName ? account.addressBookName + ' - ' : '#' + account.index + ' - ' }} {{ account.id | squeeze }}</option>
</select>
</div>
</div>
</ng-container>
<ng-container *ngIf="(inMerchantModePaymentComplete === false) && (inMerchantModeQR === false) && (pendingAccountModel !== '0')">
<div class="identicon-name-row uk-text-truncate">
<app-nano-identicon scale="6" [accountID]="pendingAccountModel" [settingIdenticonsStyle]="settings.settings.identiconsStyle" class="nano-identicon" *ngIf="(settings.settings.identiconsStyle !== 'none')"></app-nano-identicon>
<div class="account-label uk-text-truncate">{{ selectedAccountAddressBookName }}</div>
</div>
<app-nano-account-id [accountID]="pendingAccountModel" middle="off" class="nano-address-monospace uk-width-auto"></app-nano-account-id>
<button
class="uk-width-auto uk-button uk-button-secondary uk-text-center nlt-icon-button uk-margin-small-top"
type="button"
(click)="unsetSelectedAccount()"
>
<span class="nlt-icon" uk-icon="icon: pencil;"></span>
Change account
</button>
<div
class="uk-width-1-1 uk-card uk-card-default uk-margin-bottom"
style="margin-top: 60px;"
>
<div class="uk-card-body">
<p class="uk-text-large">Enter the <span class="uk-text-primary">requested amount</span></p>
<div class="form-amount">
<div class="uk-width-1-1 uk-inline">
<label class="uk-form-icon uk-link-reset uk-link-muted xno-symbol" for="form-horizontal-amount"></label>
<input [(ngModel)]="amountNano" [ngClass]="{ 'uk-form-danger': !validNano }" [attr.disabled]="pendingAccountModel == '0' || null" autocomplete="off" class="uk-input" id="form-horizontal-amount" (input)="nanoAmountChange()" style="padding-left: 52px !important;" type="number" step="any" placeholder="Amount of XNO" maxlength="40">
</div>
<div style="margin-top: 10px;" *ngIf="settings.settings.displayCurrency">
<p class="text-half-muted" style="margin: 0 0 14px 0;">or</p>
<div class="uk-width-1-1 uk-inline">
<a class="uk-form-icon uk-link-reset uk-link-muted fiat-currency-ticker" uk-tooltip title="Last Price: {{ price.price.lastPrice | fiat: settings.settings.displayCurrency }} / XNO">{{ settings.settings.displayCurrency | currencySymbol }}</a>
<input [(ngModel)]="amountFiat" [ngClass]="{ 'uk-form-danger': !validFiat }" [attr.disabled]="pendingAccountModel == '0' || null" autocomplete="off" (input)="fiatAmountChange()" style="padding-left: 52px !important;" class="uk-input" id="form-horizontal-text-fiat" type="number" step="any" placeholder="Amount of {{ settings.settings.displayCurrency }}">
</div>
</div>
</div>
<button
class="uk-width-1-1 uk-button uk-button-primary uk-text-center nlt-icon-button uk-margin-medium-top"
type="button"
(click)="merchantModeShowQR()"
>
<span class="merchant-mode-icon-qr-code"></span>
Create QR code
</button>
</div>
</div>
</ng-container>
<ng-container *ngIf="(inMerchantModeQR === true)">
<p class="uk-text-large uk-margin-remove-bottom">Send</p>
<p class="uk-text-large uk-text-primary uk-margin-remove-top" *ngIf="(amountNano === '')">
<span class="merchant-mode-currency-name">XNO</span>
</p>
<p class="uk-text-large uk-text-primary uk-margin-remove-top" *ngIf="(amountNano !== '')">
<span
class="merchant-mode-amount-integer"
>{{ merchantModeRawRequestedQR | rai: 'mnano,true' | amountsplit: 0 }}</span>
<span
class="merchant-mode-amount-fractional"
>{{ merchantModeRawRequestedQR | rai: 'mnano,true' | amountsplit: 1 }}</span>
<span class="merchant-mode-currency-name merchant-mode-currency-margin">XNO</span>
</p>
<p class="text-half-muted" style="margin: -16px 0 10px 0;">to</p>
<app-nano-account-id
[accountID]="pendingAccountModel" middle="break"
class="nano-address-monospace uk-width-auto"
style="display: inline-block; max-width: 325px;"
></app-nano-account-id>
<div
class="uk-width-1-1 uk-flex uk-flex-center uk-margin-top"
style="margin-bottom: 40px;"
>
<img class="merchant-mode-qr-code" [src]="qrCodeImage" alt="QR code" *ngIf="qrCodeImage">
<div class="merchant-mode-qr-code" *ngIf="!qrCodeImage"><div uk-spinner="ratio: 2;"></div></div>
</div>
<div
class="uk-width-1-1 uk-card uk-card-default uk-margin-bottom"
*ngFor="let prompt of merchantModePrompts; let promptIdx = index"
>
<div class="uk-card-body">
<p
*ngIf="(amountNano === '')"
class="uk-text-large text-half-muted uk-margin-remove-bottom"
>
Received
</p>
<p
*ngIf="(amountNano !== '') && prompt.moreThanRequested"
class="uk-text-large text-half-muted uk-margin-remove-bottom"
>
Received <span class="uk-text-success">more</span> than requested
</p>
<p
*ngIf="(amountNano !== '') && prompt.lessThanRequested"
class="uk-text-large text-half-muted uk-margin-remove-bottom"
>
Received <span class="uk-text-warning">less</span> than requested
</p>
<p
class="uk-text-large uk-margin-remove-top"
[class.uk-text-success]="prompt.moreThanRequested"
[class.uk-text-warning]="prompt.lessThanRequested"
>
<span
class="merchant-mode-amount-integer"
>{{ prompt.amountRaw | rai: 'mnano,true' | amountsplit: 0 }}</span>
<span
class="merchant-mode-amount-fractional"
>{{ prompt.amountRaw | rai: 'mnano,true' | amountsplit: 1 }}</span>
<span class="merchant-mode-currency-name merchant-mode-currency-margin">XNO</span>
</p>
<div
*ngIf="prompt.amountHiddenRaw.gt(0)"
class="uk-text-small text-half-muted block-hash-monospace"
style="margin: -22px 0 28px 0;"
>
+{{ prompt.amountHiddenRaw.toString(10) }} raw
</div>
<button
*ngIf="prompt.moreThanRequested"
class="uk-width-1-1 uk-button uk-button-primary uk-text-center nlt-icon-button uk-margin-bottom nlt-button-green"
type="button"
(click)="merchantModeMarkCompleteFromPrompt(prompt)"
>
<span class="nlt-icon" uk-icon="icon: check;" style="margin-top: -2px;"></span>
Mark Payment Complete
</button>
<button
*ngIf="prompt.lessThanRequested"
class="uk-width-1-1 uk-button uk-button-primary uk-text-center nlt-icon-button uk-margin-bottom"
type="button"
(click)="merchantModeSubtractAmountFromPrompt(prompt, promptIdx)"
>
<span class="nlt-icon" uk-icon="icon: pencil;" style="margin-top: -2px;"></span>
Subtract From Amount
</button>
<button
class="uk-width-1-1 uk-button uk-button-secondary uk-text-danger uk-text-center nlt-icon-button"
type="button"
(click)="merchantModeDiscardPrompt(promptIdx)"
>
<span class="nlt-icon" uk-icon="icon: close;" style="margin-top: -2px;"></span>
Discard As Unrelated
</button>
</div>
</div>
<ng-container *ngIf="merchantModePrompts.length === 0">
<p class="uk-text-large uk-margin-top text-half-muted">Awaiting payment...</p>
<button
class="uk-width-auto uk-button uk-text-center nlt-icon-button"
[class.uk-button-primary]="!loadingIncomingTxList"
[class.uk-button-secondary]="loadingIncomingTxList"
[class.uk-disabled]="loadingIncomingTxList"
type="button"
(click)="!loadingIncomingTxList && getPending()"
>
<span class="nlt-icon" uk-icon="icon: refresh;" style="margin-top: -2px;" *ngIf="!loadingIncomingTxList"></span>
<span class="spinner" uk-spinner="ratio: 0.5;" *ngIf="loadingIncomingTxList"></span>
{{ loadingIncomingTxList ? 'Checking Payment...' : 'Check Payment' }}
</button>
</ng-container>
<div
class="merchant-mode-exit uk-flex uk-flex-middle uk-hidden@s"
style="margin-top: 70px;"
(click)="merchantModeHideQR"
>
<span uk-icon="icon: close; ratio: 1.4;" class="uk-margin-small-right" style="margin-top: 2px;"></span>
<span class="label">Cancel Payment</span>
</div>
</ng-container>
<ng-container *ngIf="(inMerchantModePaymentComplete === true)">
<p class="uk-text-large uk-margin-remove-bottom uk-text-success">Received</p>
<p class="uk-text-large uk-text-success uk-margin-remove-top">
<span
class="merchant-mode-amount-integer"
>{{ merchantModeRawReceivedTotal | rai: 'mnano,true' | amountsplit: 0 }}</span>
<span
class="merchant-mode-amount-fractional"
>{{ merchantModeRawReceivedTotal | rai: 'mnano,true' | amountsplit: 1 }}</span>
<span class="merchant-mode-currency-name merchant-mode-currency-margin">XNO</span>
</p>
<div
*ngIf="merchantModeRawReceivedTotalHiddenRaw.gt(0)"
class="uk-text-small text-half-muted block-hash-monospace"
style="margin: -22px 0 28px 0;"
>
+{{ merchantModeRawReceivedTotalHiddenRaw.toString(10) }} raw
</div>
<img style="width: 50%; margin-top: -40px;" src="data:image/svg+xml,%3Csvg width='200mm' height='200mm' viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m157.05 40.584c-2.5562-2e-6 -5.1127 0.97964-7.0714 2.9383l-65.687 65.687-34.266-34.266c-3.9174-3.9174-10.225-3.9174-14.142 1e-6l-12.915 12.915c-3.9174 3.9174-3.9174 10.225-1e-6 14.142l39.526 39.526c0.4262 0.72955 0.95143 1.4181 1.5782 2.0448l12.915 12.915c3.9174 3.9174 10.225 3.9174 14.142 0l85.906-85.907c3.9174-3.9174 3.9174-10.224 0-14.142l-12.915-12.915c-1.9587-1.9587-4.5147-2.9383-7.0709-2.9383z' fill='none' stop-color='%23000000' stroke='%2332d296' stroke-linejoin='round' stroke-width='2' style='paint-order:stroke fill markers'/%3E%3C/svg%3E" />
<p class="uk-flex uk-flex-center uk-flex-middle text-half-muted uk-margin-remove-bottom">
<button
class="uk-button uk-button-secondary uk-text-center nlt-icon-button"
type="button"
ngxClipboard
[cbContent]="(
'Amount Requested: '
+ ( merchantModeRawRequestedTotal | rai: 'mnano,true' | amountsplit: 0 )
+ ( merchantModeRawRequestedTotal | rai: 'mnano,true' | amountsplit: 1 )
+ ' XNO'
+ '\n\n'
+ 'Amount Paid: '
+ ( merchantModeRawReceivedTotal | rai: 'mnano,true' | amountsplit: 0 )
+ ( merchantModeRawReceivedTotal | rai: 'mnano,true' | amountsplit: 1 )
+ ' XNO'
+ '\n\n'
+ 'Transaction ID\'s:'
+ '\n- '
+ merchantModeTransactionHashes.join('\n- ')
)"
>
<span class="nlt-icon" uk-icon="icon: copy;"></span>
Copy Payment Information
</button>
</p>
<ul class="uk-width-1-1">
<li
*ngFor="let hash of merchantModeTransactionHashes"
class="uk-text-left text-half-muted block-hash-monospace"
style="word-wrap: anywhere;"
>
{{ hash }}
</li>
</ul>
<div
class="merchant-mode-exit uk-flex uk-flex-middle"
style="margin-top: 70px;"
(click)="merchantModeResetState()"
>
<span uk-icon="icon: plus-circle; ratio: 1.4;" class="uk-margin-small-right" style="margin-top: 2px;"></span>
<span class="label">New Payment</span>
</div>
</ng-container>
</div>
</div>
</div>

View file

@ -29,6 +29,7 @@ export class ReceiveComponent implements OnInit, OnDestroy {
timeoutIdClearingRecentlyCopiedState: any = null;
mobileTransactionMenuModal: any = null;
merchantModeModal: any = null;
mobileTransactionData: any = null;
selectedAccountAddressBookName = '';
@ -48,6 +49,17 @@ export class ReceiveComponent implements OnInit, OnDestroy {
validFiat = true;
qrSuccessClass = '';
inMerchantMode = false;
inMerchantModeQR = false;
inMerchantModePaymentComplete = false;
merchantModeRawRequestedQR: BigNumber = null;
merchantModeRawRequestedTotal: BigNumber = null;
merchantModeRawReceivedTotal: BigNumber = null;
merchantModeRawReceivedTotalHiddenRaw: BigNumber = null;
merchantModeSeenBlockHashes = {};
merchantModePrompts = [];
merchantModeTransactionHashes = [];
routerSub = null;
constructor(
@ -67,12 +79,17 @@ export class ReceiveComponent implements OnInit, OnDestroy {
async ngOnInit() {
const UIkit = window['UIkit'];
const mobileTransactionMenuModal = UIkit.modal('#mobile-transaction-menu-modal');
this.mobileTransactionMenuModal = mobileTransactionMenuModal;
const merchantModeModal = UIkit.modal('#merchant-mode-modal');
this.merchantModeModal = merchantModeModal;
this.routerSub = this.route.events.subscribe(event => {
if (event instanceof ChildActivationEnd) {
this.mobileTransactionMenuModal.hide();
this.merchantModeModal.hide();
}
});
@ -110,12 +127,16 @@ export class ReceiveComponent implements OnInit, OnDestroy {
this.showQrConfirmation();
setTimeout(() => this.resetAmount(), 500);
}
if ( (this.inMerchantModeQR === true) && (transaction.block.link_as_account === this.qrAccount) ) {
this.onMerchantModeReceiveTransaction(transaction);
}
}
});
}
ngOnDestroy() {
this.mobileTransactionMenuModal.hide();
this.merchantModeModal.hide();
if (this.routerSub) {
this.routerSub.unsubscribe();
}
@ -164,6 +185,14 @@ export class ReceiveComponent implements OnInit, OnDestroy {
// Blocks for selected account
this.pendingBlocksForSelectedAccount =
this.pendingBlocks.filter(block => (block.destination === selectedAccountID));
if (this.inMerchantModeQR === true) {
this.pendingBlocksForSelectedAccount.forEach(
(pendingBlock) => {
this.onMerchantModeReceiveTransaction(pendingBlock);
}
)
}
}
showMobileMenuForTransaction(transaction) {
@ -364,4 +393,140 @@ export class ReceiveComponent implements OnInit, OnDestroy {
return new BigNumber(value);
}
unsetSelectedAccount() {
this.pendingAccountModel = '0';
this.onSelectedAccountChange(this.pendingAccountModel);
}
getRawAmountWithoutTinyRaws(rawAmountWithTinyRaws) {
const tinyRaws =
rawAmountWithTinyRaws.mod(this.nano);
return rawAmountWithTinyRaws.minus(tinyRaws);
}
merchantModeResetState() {
this.unsetSelectedAccount();
this.resetAmount();
this.inMerchantModeQR = false;
this.inMerchantModePaymentComplete = false;
}
merchantModeEnable() {
this.merchantModeResetState();
this.inMerchantMode = true;
this.merchantModeModal.show();
}
merchantModeDisable() {
this.inMerchantMode = false;
this.inMerchantModeQR = false;
this.inMerchantModePaymentComplete = false;
this.merchantModeModal.hide();
}
merchantModeShowQR() {
const isRequestingAnyAmount = (this.validNano === false || Number(this.amountNano) === 0);
if(isRequestingAnyAmount === true) {
this.resetAmount();
}
this.merchantModeRawRequestedTotal =
(isRequestingAnyAmount === true)
? new BigNumber(0)
: this.util.nano.mnanoToRaw(this.amountNano);
this.merchantModeRawRequestedQR =
(isRequestingAnyAmount === true)
? new BigNumber(0)
: this.util.nano.mnanoToRaw(this.amountNano);
this.merchantModeSeenBlockHashes =
this.pendingBlocksForSelectedAccount.reduce(
(seenHashes, receivableBlock) => {
seenHashes[receivableBlock.hash] = true
return seenHashes
},
{}
);
this.merchantModeTransactionHashes = [];
this.inMerchantModeQR = true;
}
merchantModeHideQR() {
this.inMerchantModeQR = false;
}
onMerchantModeReceiveTransaction(transaction) {
if( this.merchantModeSeenBlockHashes[transaction.hash] != null ) {
return;
}
this.merchantModeSeenBlockHashes[transaction.hash] = true;
const receivedAmountWithTinyRaws = new BigNumber(transaction.amount);
const receivedAmount =
this.getRawAmountWithoutTinyRaws(receivedAmountWithTinyRaws);
const requestedAmount =
this.getRawAmountWithoutTinyRaws(this.merchantModeRawRequestedQR);
if( receivedAmount.eq(requestedAmount) ) {
this.merchantModeTransactionHashes.push(transaction.hash);
this.merchantModeMarkCompleteWithAmount(this.merchantModeRawRequestedTotal);
} else {
const transactionPrompt = {
moreThanRequested: receivedAmount.gt(requestedAmount),
lessThanRequested: receivedAmount.lt(requestedAmount),
amountRaw: receivedAmountWithTinyRaws,
amountHiddenRaw: receivedAmountWithTinyRaws.mod(this.nano),
transactionHash: transaction.hash,
}
this.merchantModePrompts.push(transactionPrompt);
}
}
merchantModeSubtractAmountFromPrompt(prompt, promptIdx) {
const subtractedRawWithTinyRaws = prompt.amountRaw;
const subtractedRaw =
this.getRawAmountWithoutTinyRaws(subtractedRawWithTinyRaws);
const newAmountRaw =
this.merchantModeRawRequestedQR.minus(subtractedRaw);
this.merchantModeRawRequestedQR = newAmountRaw;
this.changeQRAmount(newAmountRaw.toFixed());
this.merchantModeTransactionHashes.push(prompt.transactionHash);
this.merchantModePrompts.splice(promptIdx, 1);
}
merchantModeMarkCompleteFromPrompt(prompt) {
this.merchantModeTransactionHashes.push(prompt.transactionHash);
this.merchantModeMarkCompleteWithAmount(prompt.amountRaw);
}
merchantModeDiscardPrompt(promptIdx) {
this.merchantModePrompts.splice(promptIdx, 1);
}
merchantModeMarkCompleteWithAmount(amountRaw) {
this.merchantModeRawReceivedTotal = amountRaw;
this.merchantModeRawReceivedTotalHiddenRaw = amountRaw.mod(this.nano);
this.inMerchantModePaymentComplete = true;
this.inMerchantModeQR = false;
}
}

View file

@ -453,6 +453,16 @@
background: @dark-mode-background-2 !important;
}
.merchant-mode-overlay {
background: @dark-mode-background-1 !important;
.merchant-mode-logo {
background: url('../../assets/img/nault-logo-night-mode.svg');
background-size: contain;
background-repeat: no-repeat;
}
}
// Representatives page
.delegating-account-row, .representative-row {
background: @dark-mode-background-3 !important;

View file

@ -28,7 +28,7 @@ h1, h2, h3, h4, h5, .uk-text-lead, .uk-button, .uk-alert, .uk-description-list d
@media (max-width: 939px) {
.nlt-button-group button {
margin-bottom: @nlt-intro-margin / 2 !important;
margin-bottom: 20px !important;
}
h2:not(.uk-card-title):not(.uk-modal-title) {
@ -1336,6 +1336,30 @@ input[type=number] {
}
}
// Receive page
.merchant-centered-container {
.identicon-name-row {
display: flex;
flex-direction: row;
align-items: center;
height: 28px;
padding-bottom: 8px;
.nano-identicon {
display: inline-block;
width: 27px;
height: 27px;
margin-right: 12px;
margin-left: 1px;
flex-shrink: 0;
.canvas-container canvas {
border-radius: 3px;
}
}
}
}
// Representatives page
.delegating-account-row {
border-top: none !important;
@ -1527,6 +1551,14 @@ input[type=number] {
background-color: #16A670 !important;
}
.nlt-button-green {
background-color: #16A670 !important;
&:hover {
background-color: #069660 !important;
}
}
.nlt-page-intro {
margin-bottom: @nlt-intro-margin;
}