oxen-electron-gui-wallet/src/pages/wallet/send.vue

412 lines
11 KiB
Vue

<template>
<q-page class="send">
<template v-if="view_only">
<div class="q-pa-md">
{{ $t("strings.viewOnlyMode") }}
</div>
</template>
<template v-else>
<div class="q-pa-md">
<div class="row gutter-md">
<!-- Amount -->
<div class="col-6 amount">
<OxenField
:label="$t('fieldLabels.amount')"
:error="$v.newTx.amount.$error"
>
<q-input
v-model="newTx.amount"
type="number"
min="0"
:max="unlocked_balance / 1e9"
placeholder="0"
borderless
dense
@blur="$v.newTx.amount.$touch"
/>
<q-btn
color="primary"
@click="newTx.amount = unlocked_balance / 1e9"
>
{{ $t("buttons.all") }}
</q-btn>
</OxenField>
</div>
<!-- Priority -->
<div class="col-6 priority">
<OxenField :label="$t('fieldLabels.priority')">
<q-select
v-model="newTx.priority"
emit-value
map-options
:options="priorityOptions"
borderless
dense
/>
</OxenField>
</div>
</div>
<!-- Address -->
<div class="col q-mt-sm">
<OxenField
:label="$t('fieldLabels.address')"
:error="$v.newTx.address.$error"
>
<q-input
v-model.trim="newTx.address"
:placeholder="address_placeholder"
borderless
dense
@blur="$v.newTx.address.$touch"
/>
<q-btn color="primary" to="addressbook">
{{ $t("buttons.contacts") }}
</q-btn>
</OxenField>
</div>
<!-- Notes -->
<div class="col q-mt-sm">
<OxenField :label="$t('fieldLabels.notes')" optional>
<q-input
v-model="newTx.note"
class="full-width text-area-oxen"
type="textarea"
:placeholder="$t('placeholders.transactionNotes')"
borderless
dense
/>
</OxenField>
</div>
<q-checkbox
v-model="newTx.address_book.save"
:label="$t('strings.saveToAddressBook')"
/>
<div v-if="newTx.address_book.save">
<OxenField :label="$t('fieldLabels.name')" optional>
<q-input
v-model="newTx.address_book.name"
:placeholder="$t('placeholders.addressBookName')"
borderless
dense
/>
</OxenField>
<OxenField class="q-mt-sm" :label="$t('fieldLabels.notes')" optional>
<q-input
v-model="newTx.address_book.description"
type="textarea"
class="full-width text-area-oxen"
rows="2"
:placeholder="$t('placeholders.additionalNotes')"
borderless
dense
/>
</OxenField>
</div>
<!-- div required so the button falls below the checkbox -->
<div>
<q-btn
class="send-btn"
:disable="!is_able_to_send"
color="primary"
:label="$t('buttons.send')"
@click="send()"
/>
</div>
</div>
<ConfirmTransactionDialog
:show="confirmTransaction"
:amount="confirmFields.totalAmount"
:is-blink="confirmFields.isBlink"
:send-to="confirmFields.destination"
:fee="confirmFields.totalFees"
:on-confirm-transaction="onConfirmTransaction"
:on-cancel-transaction="onCancelTransaction"
/>
<q-inner-loading :showing="tx_status.sending">
<q-spinner color="primary" size="30" />
</q-inner-loading>
</template>
</q-page>
</template>
<script>
import { mapState } from "vuex";
import { required, decimal } from "vuelidate/lib/validators";
import { address, greater_than_zero } from "src/validators/common";
import OxenField from "components/oxen_field";
import WalletPassword from "src/mixins/wallet_password";
import ConfirmDialogMixin from "src/mixins/confirm_dialog_mixin";
import ConfirmTransactionDialog from "components/confirm_tx_dialog";
const objectAssignDeep = require("object-assign-deep");
// the case for doing nothing on a tx_status update
const DO_NOTHING = 10;
export default {
components: {
OxenField,
ConfirmTransactionDialog
},
mixins: [WalletPassword, ConfirmDialogMixin],
data() {
let priorityOptions = [
{ label: this.$t("strings.priorityOptions.blink"), value: 5 }, // Blink
{ label: this.$t("strings.priorityOptions.slow"), value: 1 } // Slow
];
return {
newTx: {
amount: 0,
address: "",
priority: priorityOptions[0].value,
address_book: {
save: false,
name: "",
description: ""
}
},
priorityOptions: priorityOptions,
confirmFields: {
isBlink: false,
totalAmount: -1,
destination: "",
totalFees: 0
}
};
},
computed: mapState({
theme: state => state.gateway.app.config.appearance.theme,
view_only: state => state.gateway.wallet.info.view_only,
unlocked_balance: state => state.gateway.wallet.info.unlocked_balance,
tx_status: state => state.gateway.tx_status,
is_ready() {
return this.$store.getters["gateway/isReady"];
},
is_able_to_send() {
return this.$store.getters["gateway/isAbleToSend"];
},
address_placeholder(state) {
const wallet = state.gateway.wallet.info;
const prefix = (wallet && wallet.address && wallet.address[0]) || "L";
return `${prefix}..`;
},
confirmTransaction: state => state.gateway.tx_status.code === 1
}),
validations: {
newTx: {
amount: {
required,
decimal,
greater_than_zero
},
address: {
required,
isAddress(value) {
if (value === "") return true;
return new Promise(resolve => {
address(value, this.$gateway)
.then(() => resolve(true))
.catch(() => resolve(false));
});
}
}
}
},
watch: {
tx_status: {
handler(val, old) {
if (val.code == old.code) return;
const { code, message } = val;
switch (code) {
// the "nothing", so we can update state without doing anything
// in particular
case DO_NOTHING:
break;
case 1:
this.buildDialogFieldsSend(val);
break;
case 0:
this.$q.notify({
type: "positive",
timeout: 1000,
message
});
this.$v.$reset();
this.newTx = {
amount: 0,
address: "",
priority: this.priorityOptions[0].value,
address_book: {
save: false,
name: "",
description: ""
},
note: ""
};
break;
case -1:
this.$q.notify({
type: "negative",
timeout: 3000,
message
});
break;
}
},
deep: true
},
$route(to) {
if (to.path == "/wallet/send" && to.query.hasOwnProperty("address")) {
this.autoFill(to.query);
}
}
},
mounted() {
if (
this.$route.path == "/wallet/send" &&
this.$route.query.hasOwnProperty("address")
) {
this.autoFill(this.$route.query);
}
},
methods: {
autoFill: function(info) {
this.newTx.address = info.address;
},
buildDialogFieldsSend(txData) {
// build using mixin method
this.confirmFields = this.buildDialogFields(txData);
},
onConfirmTransaction() {
// put the loading spinner up
this.$store.commit("gateway/set_tx_status", {
code: DO_NOTHING,
message: "Getting transaction information",
sending: true
});
const { name, description, save } = this.newTx.address_book;
const addressSave = {
address: this.newTx.address,
address_book: {
description,
name,
save
}
};
const note = this.newTx.note;
const isBlink = this.confirmFields.isBlink;
const relayTxData = {
isBlink,
addressSave,
note,
// you may be sending all (which calls sweep_all RPC), but this refers to
// if the relay is coming from "sweep all" on the SN tab
isSweepAll: false
};
// Commit the transaction
this.$gateway.send("wallet", "relay_tx", relayTxData);
},
onCancelTransaction() {
this.$store.commit("gateway/set_tx_status", {
code: DO_NOTHING,
message: "Cancel the transaction from confirm dialog",
sending: false
});
},
async send() {
this.$v.newTx.$touch();
if (this.newTx.amount < 0) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: this.$t("notification.errors.negativeAmount")
});
return;
} else if (this.newTx.amount == 0) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: this.$t("notification.errors.zeroAmount")
});
return;
} else if (this.newTx.amount > this.unlocked_balance / 1e9) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: this.$t("notification.errors.notEnoughBalance")
});
return;
} else if (this.$v.newTx.amount.$error) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: this.$t("notification.errors.invalidAmount")
});
return;
}
if (this.$v.newTx.address.$error) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: this.$t("notification.errors.invalidAddress")
});
return;
}
// must wait for the dialog to be returned
let passwordDialog = await this.showPasswordConfirmation({
title: this.$t("dialog.transfer.title"),
noPasswordMessage: this.$t("dialog.transfer.message"),
ok: {
label: this.$t("dialog.transfer.ok"),
color: "primary"
}
});
passwordDialog
.onOk(password => {
password = password || "";
this.$store.commit("gateway/set_tx_status", {
code: DO_NOTHING,
message: "Getting transaction information",
sending: true
});
const newTx = objectAssignDeep.noMutate(this.newTx, {
password
});
this.$gateway.send("wallet", "transfer", newTx);
})
.onDismiss(() => {})
.onCancel(() => {});
}
}
};
</script>
<style lang="scss">
.send {
.send-btn {
margin-top: 6px;
width: 200px;
}
}
.amount {
padding-right: 10px;
}
.priority {
padding-left: 10px;
}
</style>