UNPKG

@mymonero/mymonero-page-templates

Version:

This npm package contains a number of Lit element templates that MyMonero uses for various page layouts.

979 lines (937 loc) 43.1 kB
import { html, css, LitElement } from 'lit'; import ExchangeNavigationController from "../Controllers/ExchangeNavigationController"; import { FiatApi } from "@mymonero/changenow-exchange-integration"; let fiatApi = new FiatApi({ apiKey: "b1c7ed0a20710e005b65e304b74dce3253cd9ac16009b57f4aa099f2707d64a9" }) export class ChangenowBuyWithFiatView extends ExchangeNavigationController(LitElement) { static get styles() { return css` #changenow-buy-with-fiat { margin: 20px; } .submit-button-wrapper { position: fixed; top: -45px; right: 16px; width: 15%; min-width: 41px; height: 41px; z-index: 12; } .submit-button { z-index: 13; position: fixed; right: 16px; font-weight: bold; top: -40px; z-index: 10000; } .exchange-screen-panel { margin: 15px; } .submit-button, .confirmation-button { cursor: default; border-radius: 3px; height: 24px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; text-align: center; border: none; text-decoration: none; line-height: 24px; box-sizing: border-box; width: auto; padding: 0px 8px; background-color: rgb(0, 198, 255); box-shadow: rgb(22 20 22) 0px 0.5px 1px 0px, rgb(255 255 255 / 20%) 0px 0.5px 0px 0px inset; color: rgb(22, 20, 22); -webkit-font-smoothing: subpixel-antialiased; font-size: 12px; font-weight: bold; letter-spacing: 0.5px; float: right; margin-top: 5px; -webkit-app-region: no-drag; } .hidden { display: none !important; } .form-field-title { max-width: 100%; margin: 15px 0 8px 0; user-select: none; display: block; text-align: left; color: #F8F7F8; font-family: Native-Light, input, menlo, monospace; -webkit-font-smoothing: subpixel-antialiased; font-size: 10px; letter-spacing: 0.5px; font-weight: 300; } div#currency-table { padding: 0; } .full-width { width: 100% !important; } #getOfferLoader { float: left; // min-height: 28px; padding: 0px 24px 0 0; display: none; } #getOffer { font-family: Native-Light, input, menlo, monospace; -webkit-font-smoothing: subpixel-antialiased; font-size: 10px; letter-spacing: 0.5px; font-weight: 300; color: rgb(158, 156, 158); padding-left: 0px; } .activityIndicators.graphicAndLabel > div.loader { display: inline-block; position: relative; top: 0px; } .activityIndicators.on-normal-background .loader > .block { background-color: #383638; animation: block-animate-normal-bg .75s infinite ease-in-out; } .activityIndicators .loader > .block1 { animation-delay: -1.2s !important; } .activityIndicators .loader > .block2 { animation-delay: -1.0s !important; } .activityIndicators .loader > .block3 { animation-delay: -0.8s !important; } #tx-fee { float: right; padding: 0px 13px 7px 6px; } #minimum-fee-text, #tx-fee, #addressValidationLoaderText { font-size: 10px; } #btc-address { clear: both; padding: 0px 13px 7px 0px; } #addressValidationLoader { font-family: Native-Light, input, menlo, monospace; -webkit-font-smoothing: subpixel-antialiased; font-size: 10px; letter-spacing: 0.5px; font-weight: 300; color: rgb(158, 156, 158); padding-left: 0px; padding: 0px 24px 0 0; display: none; } #addressValidationLoader .loader { float: left; } #addressValidationLoaderText { float: left; font-size: 10px; } .activityIndicators.graphicAndLabel > span { display: inline-block; } #validation-messages, #address-messages, #server-messages { max-width: fit-content; } .currencySelect { right: 5px; left: auto; } .currencySelect { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: subpixel-antialiased; font-size: 11px; font-weight: 400; letter-spacing: 0.5px; text-indent: 10px; color: rgb(223, 222, 223); background-color: rgba(80, 74, 80, 0.55); position: absolute; left: 117.5px; width: 56px; height: 29.5px; border: 0px; padding: 0px; border-radius: 0px 4px 4px 0px; -webkit-appearance: none; top: 24px; } #minimum-fee { float: right; } #minimum-fee-text, #tx-fee, #addressValidationLoaderText { font-size: 10px; margin-top: 8px; color: rgb(158, 156, 158); display: inline-block; } .full-width td { display: block; width: 100%; clear: both; } input#inCurrencyValue, input#outCurrencyValue { width: calc(100% - 70px); padding: 0px 0px; text-indent: 148px; position: relative; right: 40px; left: 0px; padding: 0px 3px; margin-right: 5px; text-indent: 20px; min-width: 104px; } span#outCurrencyValue { width: calc(100% - 70px); padding: 0px 0px; text-indent: 148px; position: relative; right: 40px; left: 0px; padding: 0px 3px; margin-right: 5px; text-indent: 20px; min-width: 104px; } .textInput { display: inline-block; height: 29px; width: 80px; border-radius: 4px; border: 1px solid rgba(0, 0, 0, 0); text-align: right; font-size: 13px; font-weight: 200; padding: 0px 63px 0px 7px; font-family: Native-Light, input, menlo, monospace; outline: none; box-shadow: rgba(56, 54, 56, 0.5) 0px 0.5px 0px 0px, rgb(22, 20, 22) 0px 0.5px 0px 0px inset; color: rgb(223, 222, 223); background-color: rgb(29, 27, 29); } .currencySelect { right: 5px; left: auto; } .longTextInput { display: block; height: 29px; width: calc((100% - 2px) - 14px); border-radius: 4px; border: 1px solid rgba(0, 0, 0, 0); text-align: left; font-size: 13px; font-weight: 200; padding: 0px 7px; font-family: Native-Light, input, menlo, monospace; outline: none; box-shadow: rgba(56, 54, 56, 0.5) 0px 0.5px 0px 0px, rgb(22, 20, 22) 0px 0.5px 0px 0px inset; color: rgb(223, 222, 223); background-color: rgb(29, 27, 29); } #inCurrencySelector { float: right; } #currency-loader { float: right; } .even-row { background-color: #3f3e3f !important; } .odd-row { background-color: rgb(56, 54, 56) !important; } /** Exchange estimate details */ .estimate-label { margin: 10px 0px 0px 15px; font-size: 13px; font-family: Native-Light, input, menlo, monospace; font-weight: 800; -webkit-font-smoothing: subpixel-antialiased; } .estimate-value { margin: 10px 15px 0px 0px; font-size: 13px; font-family: Native-Light, input, menlo, monospace; font-weight: 100; -webkit-font-smoothing: subpixel-antialiased; } #estimate-details { display: block; outline: none; height: auto; width: 100%; padding: 0px; box-sizing: border-box; appearance: none; background: rgb(56, 54, 56); border-width: 0px; box-shadow: rgb(22 20 22) 0px 0.5px 1px 0px, rgb(73 71 73) 0px 0.5px 0px 0px inset; border-radius: 5px; text-align: left; font-size: 14px; color: rgb(252, 251, 252); } .estimate-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0px; width: 100%; padding: 10px 0px 20px; } .estimate-row:first-of-type { border-radius: 10px 10px 0px 0px; } .estimate-row:last-of-type { border-radius: 0px 0px 10px 10px; } .wallet-select-wrapper { position: relative; } `; } static get properties() { return { /* Display values */ displayLoadingScreen: { type: Boolean }, displayEstimateRetrieval: { type: Boolean }, displayOrderScreen: { type: Boolean }, displayPurchaseButton: { type: Boolean }, displayPurchaseRedirectIndicator: { type: Boolean }, displayErrorString: { type: Boolean }, displayEnterCurrencyPrompt: { type: Boolean }, errorString: { type: String }, fiatCurrencies: { type: Array, reflect: true }, estimatedFiatRange: { type: Object }, estimatedCryptoRange: { type: Object }, estimateDetails: { type: Object }, estimatedFiatRangeString: { type: String }, estimatedCryptoRangeString: { type: String }, inCurrencyCode: { type: String }, inCurrencyName: { type: String }, inCurrencyValue: { type: String }, outCurrencyCode: { type: String }, outCurrencyName: { type: String }, outCurrencyValue: { type: String }, estimateUsingFiat: { type: Boolean }, context: { type: Object }, redirectUrl: { type: String }, selectedWallet: { type: Object} } } // This function listens for a custom event dispatched from the select box. // It uses the details to update the desired currency async updateSelectedCurrency(event) { this.clearEstimate(); let prepopulatedCurrencyValueExists = false; // Handle case where the user has input the amount already if ((this.inCurrencyCode !== "---") && (this.inCurrencyValue.length > 0)) { prepopulatedCurrencyValueExists = true; } this.fiatMinMaxString = ""; this.inCurrencyCode = event.detail.selectValue; this.inCurrencyName = event.detail.selectText; this.displayMinMaxLoadingIndicator = true; if (prepopulatedCurrencyValueExists) { this.handleCurrencyInputResponse(); } let rangeQueryArray = [this.fiatApi.getMinMaxRange(this.inCurrencyCode, "XMR")] let [estimatedFiatRange] = await Promise.all(rangeQueryArray) .catch(error => { // console.error(error); // console.error(error.message); this.fiatMinMaxString = "There was an error retrieving the minimum and maximum values for the specified currency"; }); this.displayMinMaxLoadingIndicator = false; this.estimatedFiatRange = estimatedFiatRange; //this.estimatedCryptoRange = estimatedCryptoRange; //this.estimatedCryptoRangeString = `${estimatedCryptoRange.min} - ${estimatedCryptoRange.max}` let formatOptions = { style: 'currency', currency: this.inCurrencyCode } let currencyFormatter = new Intl.NumberFormat(undefined, formatOptions) this.estimatedFiatRange.min = currencyFormatter.format(estimatedFiatRange.min); this.estimatedFiatRange.max = currencyFormatter.format(estimatedFiatRange.max); this.estimatedFiatRangeString = `${this.estimatedFiatRange.min} - ${this.estimatedFiatRange.max}` this.fiatMinMaxString = `You can exchange between ${estimatedFiatRange.min} ${this.inCurrencyCode} and ${estimatedFiatRange.max} ${this.inCurrencyCode}` } /** * This function initialises the selected currency and the input value */ async initSelectedCurrency() { this.fiatMinMaxString = "Busy loading minimum and maximum values for Euro"; this.inCurrencyCode = "EUR" this.inCurrencyName = "Euro" this.displayMinMaxLoadingIndicator = false; let rangeQueryArray = [this.fiatApi.getMinMaxRange(this.inCurrencyCode, "XMR")] let [estimatedFiatRange] = await Promise.all(rangeQueryArray) .catch(error => { // console.error(error); // console.error(error.message); this.fiatMinMaxString = "There was an error retrieving the minimum and maximum values for the specified currency"; }); this.displayMinMaxLoadingIndicator = false; this.estimatedFiatRange = estimatedFiatRange; // this.estimatedCryptoRange = estimatedCryptoRange; // this.estimatedCryptoRangeString = `${estimatedCryptoRange.min} - ${estimatedCryptoRange.max}` let formatOptions = { style: 'currency', currency: this.inCurrencyCode } let currencyFormatter = new Intl.NumberFormat(undefined, formatOptions) this.estimatedFiatRange.min = currencyFormatter.format(estimatedFiatRange.min); this.estimatedFiatRange.max = currencyFormatter.format(estimatedFiatRange.max); this.estimatedFiatRangeString = `${this.estimatedFiatRange.min} - ${this.estimatedFiatRange.max}` this.fiatMinMaxString = `You can exchange between ${estimatedFiatRange.min} ${this.inCurrencyCode} and ${estimatedFiatRange.max} ${this.inCurrencyCode}` } async fireEstimateEvent(event) { let options = { detail: { }, bubbles: true, composed: true }; let estimatePostEvent = new CustomEvent("fire-estimate-event", options) this.dispatchEvent(estimatePostEvent, options) let estimateResponse = await this.fiatApi.createExchangeTransaction(this.inCurrencyValue, this.inCurrencyCode, "XMR", this.selectedWallet.public_address); // todo -- service fee can be array of multiple fees -- bank fee not always charged const estimateDetails = { convertedAmount: estimateResponse.convertedAmount, expected_to_amount: estimateResponse.expected_to_amount, estimatedExchangeRate: estimateResponse.estimate_breakdown.estimatedExchangeRate, estimatedExchangeRateString: estimateResponse.estimate_breakdown.estimatedExchangeRate + " " + estimateResponse.to_currency, id: estimateResponse.id, initial_from_currency: estimateResponse.initial_from_currency, initial_expected_from_amount: estimateResponse.expected_from_amount, networkFee: estimateResponse.estimate_breakdown.networkFee, redirected_amount: estimateResponse.redirected_amount, serviceFees: estimateResponse.estimate_breakdown.serviceFees, serviceFeeString: "N/A", bankFeeString: "N/A", to_currency: estimateResponse.to_currency, networkFeeString: estimateResponse.estimate_breakdown.networkFee.amount + " " + estimateResponse.estimate_breakdown.networkFee.currency } const serviceFees = estimateResponse.serviceFees; estimateResponse.estimate_breakdown.serviceFees.forEach((fee) => { if (fee.name.toUpperCase() === "BANK FEE") { estimateDetails.bankFeeString = fee.amount + " " + fee.currency } else if (fee.name.toUpperCase() == "SERVICE FEE") { estimateDetails.serviceFeeString = fee.amount + " " + fee.currency } }) this.redirectUrl = estimateResponse.redirect_url; this.estimateDetails = estimateDetails; } async redirectToURL() { this.displayPurchaseButton = false; this.displayPurchaseRedirectIndicator = true; try { let estimateResponse = await this.fiatApi.createExchangeTransaction(this.inCurrencyValue, this.inCurrencyCode, "XMR", this.selectedWallet.public_address); this.openExternal(estimateResponse.redirect_url) this.displayPurchaseRedirectIndicator = false; } catch (error) { console.error("Failure with redirect"); // Error communicating with server to retrieve response -- show error this.errorString = error.message; } } async openExternal(url) { // Check whether we're on desktop, or web and Android if (typeof(this.context.shell) !== "undefined") { // Electron passes the shell variable as part of context this.context.shell.openExternal(url); } else if (typeof(this.context.deviceInfo) !== "undefined" && this.context.deviceInfo.platform == "ios") { await this.context.capacitorBrowser.open({ url: url }); } else { // Web and Android Capacitor codebase window.open(url, "_blank"); } } renderStyles() { // These styles are necessary in instances where we have a top-right action button let styleElement = document.getElementById("lit-styles"); if (styleElement === null) { let styles = document.createElement("style"); styles.innerHTML = ` #stack-view-stage-view { z-index: 21 !important; } #leftBarButtonHolderView { z-index: 10; } #rightBarButtonHolderView { display: none; } #navigation-bar-view-sub-wrapper { display: none; } changenow-buy-with-fiat-view { z-index: 50; } ` styles.id = "lit-styles"; let navigationView = document.getElementById("NavigationBarView"); navigationView.appendChild(styles); } } updateSelectedWallet(event) { // Updates wallet on primary form this.selectedWallet = event.detail.wallet; this.displayEnterCurrencyPrompt = false; } async connectedCallback() { super.connectedCallback(); this.renderStyles(); this.fiatApi = fiatApi; this.wallets = this.context.walletsListController.records; //this.initSelectedCurrency.bind(this); this.updateSelectedCurrency.bind(this); this.handleCurrencyInputResponse(); this.addEventListener('searchable-select-update', this.updateSelectedCurrency); this.addEventListener('wallet-selector-update', this.updateSelectedWallet); this.displayLoadingScreen = true; this.displayOrderScreen = true; this.displayMinMaxLoadingIndicator = true; let apiIsAvailable = await this.checkAPIIsAvailable(); let enabledCurrencies = []; this.initSelectedCurrency(); if (apiIsAvailable) { this.displayLoadingScreen = false; this.displayOrderScreen = true; try { let fiatCurrencies = await this.fiatApi.getAvailableFiatCurrencies(); for (let i = 0; i < fiatCurrencies.length; i++) { var hasValidPaymentMethod = false; let paymentMethodArray = fiatCurrencies[i].networks[0].payment_methods; for (let j = 0; j < paymentMethodArray.length; j++) { if (paymentMethodArray[j].deposit_enabled === true) { hasValidPaymentMethod = true; } } if (hasValidPaymentMethod) { enabledCurrencies.push(fiatCurrencies[i]); } } this.fiatCurrencies = enabledCurrencies; this.requestUpdate(); // TODO: Check if this is necessary //this.displayLoadingScreen = false; this.displayCurrencyLoadingIndicator = false; this.displayOrderScreen = true; } catch (error) { this.displayErrorResponse = true; this.displayErrorString = error.message; } } else { console.error("Our exchange partner is temporarily unavailable. Please try again later."); } // 1. checkAPIIsAvailable // -- If available, go to step 2 // -- If unavailable, show 'Unavailaility' message (Maybe we should grey out the panel on the previous page) // 2. Get a list of available fiat Currencies // 3. Populate select box with available currencies // 4. Re-order select options to prioritise popular currencies eg. USD, GBP, JPY // 5. Select dropdown should support typing (search against ticker code and against currency's full name) // 6. Once currency is selected: // -- Show loader // -- Async (keep reference): Get Fiat range estimate (getMinMaxRange(fiat_currency, cryptocurrency)) // -- Once returned: // --- Update UI to display minimum and maximum amounts // --- Enable desired amount fields // -- If keypress before return // --- Cancel previous axios request // --- Send new async request (while storing a reference) // -- Hide loader // 8. Handle input events // -- Check number is valid float // -- If float, start async request and keep reference // --- When request returns, update currency fields as appropriate // -- If async query sent and keypress occurs, cancel async request } // Returns true when API is available. // Returns false when API is unavailable // Throws errors in any other scenario async checkAPIIsAvailable() { try { let response = await this.fiatApi.getFiatAPIStatus(); if (response.message == "OK") { return true; } else { return false; } } catch(error) { // TODO: build out better error handling console.error("API not available -- network error or unexpected error or ChangeNow response object's format changed") } } async getTransactionEstimate() { try { let response = await this.fiatApi.getTransactionEstimate(); if (response.message == "OK") { return true; } else { return false; } } catch(error) { // TODO: build out better error handling console.error("API not available -- network error or unexpected error or ChangeNow response object's format changed") } } constructor() { super(); this.context = {}; this.displayLoadingScreen = false; this.displayEstimateRetrieval = false; this.displayOrderScreen = false; this.displayConfirmationScreen = false; this.displayCurrencyLoadingIndicator = true; this.displayPurchaseButton = true; this.errorString = false; this.clickHandler = this.clickHandler; this.estimateDetails = { convertedAmount: "", expected_to_amount: "", estimatedExchangeRate: "", estimatedExchangeRateString: "", id: "", initial_from_currency: "", initial_expected_from_amount: "", networkFee: "", redirected_amount: "", serviceFees: "", serviceFeeString: "", bankFeeString: "", to_currency: "", networkFeeString: "" }; this.estimatedFiatRange = {}; this.estimatedFiatRangeString = ""; this.estimatedCryptoRangeString = ""; this.estimatedCryptoRange = {}; this.fiatMinMaxString = ""; this.inCurrencyCode = "EUR"; this.inCurrencyName = "Euro"; this.inCurrencyValue = "200"; this.outCurrencyCode = "XMR"; this.outCurrencyName = "Monero"; this.outCurrencyValue = ""; this.estimateUsingFiat = true; this.outCurrencyValue = ""; this.redirectUrl = ""; this.fiatCurrencies = [{ "block_explorer_url_mask": null, "currency_type": "FIAT", "default_exchange_value": "300", "enabled": true, "has_external_id": false, "id": "4881817401", "is_available": null, "is_featured": null, "is_stable": null, "logo_url": "", "name": "", "networks": [], "ticker": "", } ]; this.selectedFiatCurrency = "EUR"; //this.setScreenTitle("Buy Monero With Fiat"); this.wallets = [ {} ]; } clearEstimate() { // We cannot unset the keys on this object because rendering depends on it this.displayPurchaseButton = true; this.estimateDetails = { convertedAmount: "", expected_to_amount: "", estimatedExchangeRate: "", estimatedExchangeRateString: "", id: "", initial_from_currency: "", initial_expected_from_amount: "", networkFee: "", redirected_amount: "", serviceFees: "", serviceFeeString: "", bankFeeString: "", to_currency: "", networkFeeString: "" }; } handleCurrencyInput(event) { this.clearEstimate(); var eventPath = event.path || (event.composedPath && event.composedPath()); if (eventPath[0].id == "inCurrencyValue") { this.inCurrencyValue = eventPath[0].value; } else if (eventPath[0].id == "outCurrencyValue") { this.outCurrencyValue = eventPath[0].value; } if (this.estimateRequestTimer !== 'undefined') { clearTimeout(this.estimateRequestTimer); } this.estimateRequestTimer = setTimeout(() => { if (this.inCurrencyCode.length > 0) { // TODO: Put this into standalone function this.handleCurrencyInputResponse(); } else { this.displayEnterCurrencyPrompt = true; } }, 2000); } async handleCurrencyInputResponse() { this.displayEstimateRetrieval = true; this.displayErrorResponse = false; try { let response = await this.fiatApi.getTransactionEstimate(this.inCurrencyValue, this.inCurrencyCode, "XMR"); let estimateDetails = {}; estimateDetails.convertedAmount = response.converted_amount.amount; //estimateDetails.expected_to_amount = response.expected_to_amount; estimateDetails.estimatedExchangeRate = response.estimated_exchange_rate; estimateDetails.estimatedExchangeRateString = response.estimated_exchange_rate + " " + response.to_currency; //estimateDetails.id = response.id; estimateDetails.initial_from_currency = response.from_currency; estimateDetails.initial_expected_from_amount = this.inCurrencyValue; estimateDetails.networkFee = response.network_fee.amount; //estimateDetails.redirected_amount = response.redirected_amount; estimateDetails.serviceFees = response.service_fees; estimateDetails.to_currency = response.to_currency; //this.outCurrencyValue = response.value; estimateDetails.expected_to_amount = response.value; estimateDetails.networkFeeString = response.network_fee.amount + " " + response.network_fee.currency estimateDetails.serviceFeeString = "N/A" estimateDetails.bankFeeString = "N/A" response.service_fees.forEach((fee) => { if (fee.name.toUpperCase() === "BANK FEE") { estimateDetails.bankFeeString = fee.amount + " " + fee.currency } else if (fee.name.toUpperCase() == "SERVICE FEE") { estimateDetails.serviceFeeString = fee.amount + " " + fee.currency } }) this.estimateDetails = estimateDetails; } catch (error) { console.error(error, error.message); if (error.isAxiosError && typeof(error.response.data) === "object" && typeof(error.response.data.message) === "string") { this.errorString = error.response.data.message; } else { this.errorString = error.message; } this.displayErrorResponse = true; } this.displayEstimateRetrieval = false; } // NB: Thoroughly test this code on Android createRenderRoot() { const root = super.createRenderRoot(); root.addEventListener('click', (event) => { if (event.target.localName == "input") { event.target.focus(); } else { let inputs = this.querySelectorAll("input"); inputs.forEach((input) => { input.blur(); }) } if (event.target.id == "confirmation-button") { this.redirectToURL(); } }); root.addEventListener('touchend', (event) => { if (event.target.localName == "input") { event.target.focus(); } else { let inputs = this.querySelectorAll("input"); inputs.forEach((input) => { input.blur(); }) } if (event.target.id == "confirmation-button") { this.redirectToURL(); } }); return root; } render() { // We're going to use conditionals and classes to determine which elements to hide return html` <div id="changenow-buy-with-fiat"> <div class="submit-button-wrapper"> &nbsp; </div> <div class="content-container empty-page-content-container"> <buy-with-fiat-loading-screen-changenow ?hidden=${!this.displayLoadingScreen}></buy-with-fiat-loading-screen-changenow> <div ?hidden=${!this.displayOrderScreen}> <div class="exchangeScreen exchange-page-panel"> <div class="content-container exchange-page-content-container" id="orderForm"> <div class="form_field wallet-select-wrapper"> <wallet-selector .wallets=${this.wallets}></wallet-selector> </div> <div class="form_field" id="currency-table"> <table class="full-width"> <tbody><tr> <td> <div class="field_title form-field-title">${this.inCurrencyName.length == 0 ? "Please select a currency" : this.inCurrencyName + " you will pay" } <div style="position: relative; left: 0px; top: 0px; padding: 2px 0 0 0;"> <span class="field_title form-field-title label-spacing" style="margin-top: 0px;">AMOUNT</span> <input id="inCurrencyValue" @input=${this.handleCurrencyInput} class="textInput currencyInput" type="text" .placeholder=${this.estimatedFiatRangeString.length > 0 ? this.estimatedFiatRangeString : "00.00" } .value=${this.inCurrencyValue}> <div id="inCurrencySelector"> <searchable-select .values=${this.fiatCurrencies}></searchable-select> <!-- <select id="inCurrencySelectList" class="currencySelect"> <option value="XMR">XMR</option> </select> --> </div> </div> <div id="transaction-range"> <span id="transaction-range" class="field_title form-field-title">${this.fiatMinMaxString}</span> <div id="currency-loader"> <!-- <activity-indicator .loadingText=${"Loading supported currencies"} ?hidden=${!this.displayCurrencyLoadingIndicator}></activity-indicator> --> <activity-indicator .loadingText=${"Retrieving minimum and maximum transaction amount limits"} ?hidden=${!this.displayMinMaxLoadingIndicator}></activity-indicator> <activity-indicator .loadingText=${"Busy retrieving estimate"} ?hidden=${!this.displayEstimateRetrieval}></activity-indicator> <div id="errorResponse" ?hidden=${!this.displayErrorResponse}> <span id="errorResponseText" class="form-field-title"> ${this.errorString} </span> </div> </div> </div> </div> </td> <td> <!-- <div id="inInputDiv" class="field_title form-field-title"><span id="outCurrencyTickerCode">XMR</span> you will receive <div class="" style="position: relative; left: 0px; top: 0px; padding: 2px 0 0 0"> <span class="field_title form-field-title label-spacing" style="margin-top: 0px;">AMOUNT</span> <span id="outCurrencyValue">${this.outCurrencyValue}</span> <!-- CN API doesn't support getting a fiat value to pay from a crypto amount yet <input id="outCurrencyValue" @input=${this.handleCurrencyInput} class="textInput currencyInput" type="text" .placeholder=${this.estimatedCryptoRangeString.length > 0 ? this.estimatedCryptoRangeString : "00.00" } .value=${this.outCurrencyValue}> <div id="outCurrencySelector"> <select id="outCurrencySelectList" class="currencySelect"> <option value="XMR">XMR</option> </select> </div> <div class="selectIndicator" style="right: 12px; top: 33px;"></div> </div> </div> --> </td> </tr> </tbody></table> </div> <div class="form_field" id="getOfferLoader"> </div> <div class="form_field" id="estimate-details"> <!-- {"id":"4900188772","status":"new","email":null,"errors":[],"status_details":null,"from_currency":"GBP","initial_from_currency":"GBP","from_network":null,"from_currency_with_network":null,"from_amount":"0","deposit_type":"VISA_MC1","payout_type":"CRYPTO_THROUGH_CN","expected_from_amount":"75.5555","initial_expected_from_amount":"75.5555","to_currency":"XMR","to_network":null,"to_currency_with_network":null,"to_amount":null,"output_hash":null,"expected_to_amount":"0.3075642","location":"ZA","created_at":"2021-09-03T09:13:04.822Z","updated_at":"2021-09-03T09:13:04.822Z","partner_id":"5833338174","external_partner_link_id":null,"estimate_breakdown":{"toAmount":"0.3075642","fromAmount":75.5555,"serviceFees":[{"name":"Bank fee","amount":"3.02222","currency":"GBP"},{"name":"Service fee","amount":"2.25","currency":"GBP"}],"convertedAmount":{"amount":"70.28328","currency":"GBP"},"estimatedExchangeRate":"0.00437606","networkFee":{"amount":"0.001","currency":"XMR"}},"payout":{"address":"47joJKcNWs66f6ein9qTTVFyzeGnmBEGWKomSuMmqwaBYGj7gv2RvFRRUy1xtzpn6qE8BBpDnuFbp44qe9X1XmK78vqXaij","extra_id":"1"},"redirect_url":"https://payments.guardarian.com/checkout?tid=4900188772"} --> <div class="estimate-wrapper" ?hidden=${ (Object.keys(this.estimateDetails).length > 0) && (Object.keys(this.estimateDetails.expected_to_amount).length == 0) }> <div class="estimate-row even-row"> <div class="estimate-label">You send</div> <div class="estimate-value"> ${this.estimateDetails.initial_expected_from_amount} ${this.estimateDetails.initial_from_currency} </div> </div> <div class="estimate-row odd-row"> <div class="estimate-label">Bank Fee</div> <div class="estimate-value">${this.estimateDetails.bankFeeString}</div> </div> <div class="estimate-row even-row"> <div class="estimate-label">Service Fee</div> <div class="estimate-value">${this.estimateDetails.serviceFeeString}</div> </div> <div class="estimate-row odd-row"> <div class="estimate-label">Network Fee</div> <div class="estimate-value">${this.estimateDetails.networkFeeString}</div> </div> <div class="estimate-row even-row"> <div class="estimate-label">Estimated Rate</div> <div class="estimate-value">${this.estimateDetails.estimatedExchangeRateString}</div> </div> <div class="estimate-row odd-row"> <div class="estimate-label">You receive </div> <div class="estimate-value">~ ${this.estimateDetails.expected_to_amount} XMR</div> </div> <div class="estimate-row even-row"> <div class="estimate-label"></div> <div class="estimate-value"> <a @click=${this.redirectToURL} ?hidden=${!this.displayPurchaseButton} class="confirmation-button" id="confirmation-button">Buy XMR</a> <activity-indicator .loadingText=${"Busy finalising estimate and redirecting you to our partner"} ?hidden=${!this.displayPurchaseRedirectIndicator}></activity-indicator> </div> </div> <div class="estimate-row odd-row"> <div class="estimate-label"></div> <div class="estimate-value">* Please note that this is an estimate, and that your final rate may differ. The exchange and the quoted rate are controlled by our third-party partner.</div> </div> </div> </div> <style> #addressValidationLoader { float: left; // min-height: 28px; } #addressValidationLoader .loader { float: left; } #addressValidationLoaderText { float: left; } .exchange-cross { color: #d80000; font-size: 18px; position: relative; top: 2px; } .exchange-tick { color: #00CD00; font-size: 18px; position: relative; top: 2px; } #addressValidationLoader div { // display: none; } #addressValidationLoader { padding: 0px 24px 0 0; display: none; } </style> </div> </div> </div> </div> </div></div></div> </div> </div> `; } } try { customElements.define('changenow-buy-with-fiat-view', ChangenowBuyWithFiatView); } catch (error) { // already defined }