Tip user completed

This commit is contained in:
Justin Ferrari 2023-01-06 14:13:13 -05:00
parent b71f7004c1
commit 631cffeb21
7 changed files with 670 additions and 581 deletions

View File

@ -543,7 +543,6 @@
"cchange40": "IMAGE (click to view)",
"cchange41": "Your Balance Is Under 4.20 QORT",
"cchange42": "Out of the need to combat spam, accounts with under 4.20 Qort balance will take a long time to SEND messages in Q-Chat. If you wish to immediately increase the send speed for Q-Chat messages, obtain over 4.20 QORT to your address. This can be done with trades in the Trade Portal, or by way of another Qortian giving you the QORT. Once you have over 4.20 QORT in your account, Q-Chat messages will be instant and this dialog will no more show. Thank you for your understanding of this necessary spam prevention method, and we hope you enjoy Qortal!"
},
"welcomepage": {
"wcchange1": "Welcome to Q-Chat",
@ -635,7 +634,15 @@
"gchange57": "TIP USER",
"gchange58": "Tip Amount",
"gchange59": "Available Balance",
"gchange60": "Failed to Fetch QORT Balance. Try again!"
"gchange60": "Failed to Fetch QORT Balance. Try again!",
"gchange61": "Current static fee",
"gchange62": "Send",
"gchange63": "Insufficient Funds!",
"gchange64": "Invalid Amount!",
"gchange65": "Receiver cannot be empty!",
"gchange66": "Invalid Receiver!",
"gchange67": "Transaction Successful!",
"gchange68": "Transaction Failed!"
},
"puzzlepage": {
"pchange1": "Puzzles",

View File

@ -47,8 +47,8 @@
"@polymer/paper-slider": "3.0.1",
"@polymer/paper-spinner": "3.0.2",
"@polymer/paper-tooltip": "3.0.1",
"@vaadin/horizontal-layout": "23.2.5",
"@vaadin/tabs": "23.2.5",
"@vaadin/horizontal-layout": "23.3.2",
"@vaadin/tabs": "23.3.2",
"@rollup/plugin-alias": "4.0.2",
"@rollup/plugin-babel": "6.0.3",
"@rollup/plugin-commonjs": "24.0.0",

View File

@ -1180,10 +1180,8 @@ class ChatPage extends LitElement {
.groupAdmin=${this.groupAdmin}
.leaveGroupObj=${this.groupInfo}
.setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)}
.chatEditor=${this.chatEditor}
>
.chatEditor=${this.chatEditor}>
</chat-right-panel>
</div>
</div>
`

View File

@ -1,16 +1,17 @@
import { LitElement, html, css } from "lit"
import { render } from "lit/html.js"
import { get, translate } from "lit-translate"
import { Epml } from "../../../epml"
import { getUserNameFromAddress } from "../../utils/getUserNameFromAddress"
import snackbar from "./snackbar.js"
import "@material/mwc-button"
import "@material/mwc-dialog"
import "@polymer/paper-spinner/paper-spinner-lite.js"
import "@material/mwc-icon"
import "./WrapperModal"
const parentEpml = new Epml({ type: "WINDOW", source: window.parent })
import { LitElement, html, css } from "lit";
import { render } from "lit/html.js";
import { get, translate } from "lit-translate";
import { Epml } from "../../../epml";
import { getUserNameFromAddress } from "../../utils/getUserNameFromAddress";
import snackbar from "./snackbar.js";
import "@material/mwc-button";
import "@material/mwc-dialog";
import "@polymer/paper-spinner/paper-spinner-lite.js";
import '@polymer/paper-progress/paper-progress.js';
import "@material/mwc-icon";
import '@vaadin/button';
import "./WrapperModal";
import "./TipUser"
class ChatRightPanel extends LitElement {
static get properties() {
@ -30,7 +31,11 @@ class ChatRightPanel extends LitElement {
openTipUser: { type: Boolean },
userName: { type: String },
chatEditor: { type: Object },
walletBalance: { type: Number }
walletBalance: { type: Number },
sendMoneyLoading: { type: Boolean },
btnDisable: { type: Boolean },
errorMessage: { type: String },
successMessage: { type: String }
}
}
@ -50,7 +55,12 @@ class ChatRightPanel extends LitElement {
this.downObserverElement = ''
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address
this.openTipUser = false
this.userName = {}
this.userName = ""
this.sendMoneyLoading = false
this.btnDisable = false
this.errorMessage = ""
this.successMessage = ""
this.setOpenTipUser = this.setOpenTipUser.bind(this);
}
static get styles() {
@ -220,54 +230,6 @@ class ChatRightPanel extends LitElement {
cursor: pointer;
color: #494c50;
}
.tip-user-header {
display: flex;
justify-content: center;
align-items: center;
padding: 12px;
border-bottom: 1px solid whitesmoke;
gap: 25px;
}
.tip-user-header-font {
font-family: Montserrat, sans-serif;
font-size: 20px;
color: var(--chat-bubble-msg-color);
}
.tip-user-body {
display: flex;
justify-content: center;
align-items: flex-start;
padding: 20px 10px;
flex-direction: column;
gap: 15px;
}
.tip-input {
width: 300px;
margin-bottom: 15px;
outline: 0;
border-width: 0 0 2px;
border-color: var(--mdc-theme-primary);
background-color: transparent;
padding: 10px;
font-family: Roboto, sans-serif;
font-size: 15px;
color: var(--chat-bubble-msg-color);
}
.tip-input::selection {
background-color: var(--mdc-theme-primary);
color: white;
}
.tip-input::placeholder {
opacity: 0.9;
color: var(--black);
}
`
}
@ -275,7 +237,6 @@ class ChatRightPanel extends LitElement {
this.viewElement = this.shadowRoot.getElementById('viewElement');
this.downObserverElement = this.shadowRoot.getElementById('downObserver');
this.elementObserver();
this.fetchWalletDetails();
}
async updated(changedProperties) {
@ -287,236 +248,6 @@ class ChatRightPanel extends LitElement {
}
}
timeIsoString(timestamp) {
let myTimestamp = timestamp === undefined ? 1587560082346 : timestamp
let time = new Date(myTimestamp)
return time.toISOString()
}
resetDefaultSettings() {
this.error = false
this.message = ""
this.isLoading = false
}
renderErr9Text() {
return html`${translate("grouppage.gchange49")}`
}
async confirmRelationship(reference) {
let interval = null
let stop = false
const getAnswer = async () => {
if (!stop) {
stop = true
try {
let myRef = await parentEpml.request("apiCall", {
type: "api",
url: `/transactions/reference/${reference}`,
})
if (myRef && myRef.type) {
clearInterval(interval)
this.isLoading = false
this.openUserInfo = false
}
} catch (error) {}
stop = false
}
}
interval = setInterval(getAnswer, 5000)
}
async unitFee(txType) {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
const url = `${nodeUrl}/transactions/unitfee?txType=${txType}`;
let fee = null;
try {
const res = await fetch(url);
const data = await res.json();
fee = (Number(data) / 1e8).toFixed(3);
} catch (error) {
fee = null;
}
return fee;
}
async getLastRef() {
let myRef = await parentEpml.request("apiCall", {
type: "api",
url: `/addresses/lastreference/${this.selectedAddress.address}`,
})
return myRef;
}
getTxnRequestResponse(txnResponse, reference) {
if (txnResponse === true) {
this.message = this.renderErr9Text()
this.error = false
this.confirmRelationship(reference)
} else {
this.error = true
this.message = ""
throw new Error(txnResponse)
}
}
async convertBytesForSigning(transactionBytesBase58) {
let convertedBytes = await parentEpml.request("apiCall", {
type: "api",
method: "POST",
url: `/transactions/convert`,
body: `${transactionBytesBase58}`,
})
return convertedBytes
}
async signTx(body){
return await parentEpml.request("apiCall", {
type: "api",
method: "POST",
url: `/transactions/sign`,
body: body,
headers: {
'Content-Type': 'application/json'
}
})
}
async process(body){
return await parentEpml.request("apiCall", {
type: "api",
method: "POST",
url: `/transactions/process`,
body: body,
})
}
async _addAdmin(groupId) {
this.resetDefaultSettings()
const leaveFeeInput = await this.unitFee('ADD_GROUP_ADMIN')
if(!leaveFeeInput){
throw Error()
}
this.isLoading = true
// Get Last Ref
const getLastRef = async () => {
let myRef = await parentEpml.request('apiCall', {
type: 'api',
url: `/addresses/lastreference/${this.selectedAddress.address}`
})
return myRef
};
const validateReceiver = async () => {
let lastRef = await getLastRef();
let myTransaction = await makeTransactionRequest(lastRef)
getTxnRequestResponse(myTransaction)
}
// Make Transaction Request
const makeTransactionRequest = async (lastRef) => {
let groupdialog3 = get("transactions.groupdialog3")
let groupdialog4 = get("transactions.groupdialog4")
let myTxnrequest = await parentEpml.request('transaction', {
type: 24,
nonce: this.selectedAddress.nonce,
params: {
_groupId: groupId,
fee: leaveFeeInput,
member: this.selectedHead.address,
lastReference: lastRef
}
})
return myTxnrequest
}
const getTxnRequestResponse = (txnResponse) => {
if (txnResponse.success === false && txnResponse.message) {
this.error = true
this.message = txnResponse.message
throw new Error(txnResponse)
} else if (txnResponse.success === true && !txnResponse.data.error) {
this.message = this.renderErr9Text()
this.error = false
this.confirmRelationship()
} else {
this.error = true
this.message = txnResponse.data.message
throw new Error(txnResponse)
}
}
validateReceiver()
}
async _removeAdmin(groupId) {
this.resetDefaultSettings()
const leaveFeeInput = await this.unitFee('REMOVE_GROUP_ADMIN')
if(!leaveFeeInput){
throw Error()
}
this.isLoading = true
// Get Last Ref
const getLastRef = async () => {
let myRef = await parentEpml.request('apiCall', {
type: 'api',
url: `/addresses/lastreference/${this.selectedAddress.address}`
})
return myRef
};
const validateReceiver = async () => {
let lastRef = await getLastRef();
let myTransaction = await makeTransactionRequest(lastRef)
getTxnRequestResponse(myTransaction)
}
// Make Transaction Request
const makeTransactionRequest = async (lastRef) => {
let groupdialog3 = get("transactions.groupdialog3")
let groupdialog4 = get("transactions.groupdialog4")
let myTxnrequest = await parentEpml.request('transaction', {
type: 25,
nonce: this.selectedAddress.nonce,
params: {
_groupId: groupId,
fee: leaveFeeInput,
member: this.selectedHead.address,
lastReference: lastRef
}
})
return myTxnrequest
}
const getTxnRequestResponse = (txnResponse) => {
if (txnResponse.success === false && txnResponse.message) {
this.error = true
this.message = txnResponse.message
throw new Error(txnResponse)
} else if (txnResponse.success === true && !txnResponse.data.error) {
this.message = this.renderErr9Text()
this.error = false
this.confirmRelationship()
} else {
this.error = true
this.message = txnResponse.data.message
throw new Error(txnResponse)
}
}
validateReceiver()
}
elementObserver() {
const options = {
root: this.viewElement,
@ -531,6 +262,7 @@ class ChatRightPanel extends LitElement {
// passing it the element to observe, and the options object
observer.observe(elementToObserve);
}
observerHandler(entries) {
if (!entries[0].isIntersecting) {
return
@ -543,29 +275,13 @@ class ChatRightPanel extends LitElement {
}
}
getApiKey() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
let apiKey = myNode.apiKey;
return apiKey;
}
async fetchWalletDetails() {
parentEpml.request('apiCall', {
url: `/addresses/balance/${this.myAddress}?apiKey=${this.getApiKey()}`,
})
.then((res) => {
if (isNaN(Number(res))) {
let snack4string = get("grouppage.gchange60")
parentEpml.request('showSnackBar', `${snack4string}`)
} else {
this.walletBalance = Number(res).toFixed(8);
}
})
setOpenTipUser(props) {
this.openTipUser = props
}
render() {
console.log('this.groupMembers', this.groupMembers);
console.log(20, "Chat Right Panel Here");
console.log(28, "Chat Right Panel Here");
const owner = this.groupAdmin.filter((admin)=> admin.address === this.leaveGroupObj.owner)
return html`
<div class="container">
@ -626,7 +342,9 @@ class ChatRightPanel extends LitElement {
<wrapper-modal
.onClickFunc=${() => {
if (this.isLoading) return
this.openUserInfo = false
this.openUserInfo = false;
this.userName = "";
this.shadowRoot.querySelector("tip-user").shadowRoot.getElementById('amountInput').value = "";
}}
style=${
this.openUserInfo ? "display: block" : "display: none"
@ -691,14 +409,13 @@ class ChatRightPanel extends LitElement {
this.chatEditor.enable();
}}
style=${this.openTipUser ? "display: block" : "display: none"}>
<div class="tip-user-header">
<img src="/img/qort.png" width="32" height="32">
<p class="tip-user-header-font">${translate("grouppage.gchange55")} ${this.userName}</p>
</div>
<div class="tip-user-body">
<p class="tip-available">${translate("grouppage.gchange59")}: ${this.walletBalance} QORT</p>
<input class="tip-input" type="number" placeholder="${translate("grouppage.gchange58")}" />
</div>
<tip-user
.openUserInfo=${this.openUserInfo}
.chatEditor=${this.chatEditor}
.userName=${this.userName}
.setOpenTipUser=${(val) => this.setOpenTipUser(val)}
>
</tip-user>
</wrapper-modal>
</div>
</div>

View File

@ -0,0 +1,85 @@
import { css } from 'lit'
export const tipUserStyles = css`
.tip-user-header {
display: flex;
justify-content: center;
align-items: center;
padding: 12px;
border-bottom: 1px solid whitesmoke;
gap: 25px;
user-select: none;
}
.tip-user-header-font {
font-family: Montserrat, sans-serif;
font-size: 20px;
color: var(--chat-bubble-msg-color);
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tip-user-body {
display: flex;
justify-content: center;
align-items: center;
padding: 20px 10px;
flex-direction: column;
gap: 25px;
}
.tip-input {
width: 300px;
margin-bottom: 15px;
outline: 0;
border-width: 0 0 2px;
border-color: var(--mdc-theme-primary);
background-color: transparent;
padding: 10px;
font-family: Roboto, sans-serif;
font-size: 15px;
color: var(--chat-bubble-msg-color);
}
.tip-input::selection {
background-color: var(--mdc-theme-primary);
color: white;
}
.tip-input::placeholder {
opacity: 0.9;
color: var(--black);
}
.tip-available {
font-family: Roboto, sans-serif;
font-size: 17px;
color: var(--chat-bubble-msg-color);
font-weight: 300;
letter-spacing: 0.3px;
margin: 0;
user-select: none;
}
.success-msg {
font-family: Roboto, sans-serif;
font-size: 18px;
font-weight: 400;
letter-spacing: 0.3px;
margin: 0;
user-select: none;
color: #10880b;
}
.error-msg {
font-family: Roboto, sans-serif;
font-size: 18px;
font-weight: 400;
letter-spacing: 0.3px;
margin: 0;
user-select: none;
color: #f30000;
}
`

View File

@ -0,0 +1,282 @@
import { LitElement, html } from 'lit';
import { render } from 'lit/html.js';
import { get, translate } from 'lit-translate';
import { tipUserStyles } from './TipUser-css.js';
import { Epml } from '../../../epml';
import '@vaadin/button';
import '@polymer/paper-progress/paper-progress.js';
const parentEpml = new Epml({ type: "WINDOW", source: window.parent });
export class TipUser extends LitElement {
static get properties() {
return {
userName: { type: String },
chatEditor: { type: Object },
walletBalance: { type: Number },
sendMoneyLoading: { type: Boolean },
openUserInfo: { type: Boolean },
btnDisable: { type: Boolean },
errorMessage: { type: String },
successMessage: { type: String },
setOpenTipUser: { attribute: false }
}
}
constructor() {
super()
this.sendMoneyLoading = false
this.btnDisable = false
this.errorMessage = ""
this.successMessage = ""
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress
}
static styles = [tipUserStyles]
async firstUpdated() {
await this.fetchWalletDetails();
}
updated(changedProperties) {
if (changedProperties && changedProperties.has("openUserInfo")) {
if (this.openUserInfo) {
this.shadowRoot.getElementById("amountInput").value = "";
this.errorMessage = "";
this.successMessage = "";
}
}
}
async getLastRef() {
let myRef = await parentEpml.request("apiCall", {
type: "api",
url: `/addresses/lastreference/${this.myAddress.address}`,
})
return myRef;
}
renderSuccessText() {
return html`${translate("grouppage.gchange67")}`
}
renderReceiverText() {
return html`${translate("grouppage.gchange66")}`
}
getApiKey() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
let apiKey = myNode.apiKey;
return apiKey;
}
async fetchWalletDetails() {
await parentEpml.request('apiCall', {
url: `/addresses/balance/${this.myAddress.address}?apiKey=${this.getApiKey()}`,
})
.then((res) => {
if (isNaN(Number(res))) {
let snack4string = get("grouppage.gchange60")
parentEpml.request('showSnackBar', `${snack4string}`)
} else {
this.walletBalance = Number(res).toFixed(8);
}
})
}
async sendQort() {
const amount = this.shadowRoot.getElementById("amountInput").value;
let recipient = this.userName;
this.sendMoneyLoading = true;
this.btnDisable = true;
if (parseFloat(amount) + parseFloat(0.001) > parseFloat(this.walletBalance)) {
this.sendMoneyLoading = false;
this.btnDisable = false;
let snack1string = get("grouppage.gchange63");
parentEpml.request('showSnackBar', `${snack1string}`);
return false;
}
if (parseFloat(amount) <= 0) {
this.sendMoneyLoading = false;
this.btnDisable = false;
let snack2string = get("grouppage.gchange64");
parentEpml.request('showSnackBar', `${snack2string}`);
return false;
}
if (recipient.length === 0) {
this.sendMoneyLoading = false;
this.btnDisable = false;
let snack3string = get("grouppage.gchange65");
parentEpml.request('showSnackBar', `${snack3string}`);
return false;
}
const validateName = async (receiverName) => {
let myRes;
let myNameRes = await parentEpml.request('apiCall', {
type: 'api',
url: `/names/${receiverName}`,
})
if (myNameRes.error === 401) {
myRes = false;
} else {
myRes = myNameRes;
}
return myRes;
}
const validateAddress = async (receiverAddress) => {
let myAddress = await window.parent.validateAddress(receiverAddress);
return myAddress;
}
const validateReceiver = async (recipient) => {
let lastRef = await this.getLastRef();
let isAddress;
try {
isAddress = await validateAddress(recipient);
} catch (err) {
isAddress = false;
}
if (isAddress) {
let myTransaction = await makeTransactionRequest(recipient, lastRef);
getTxnRequestResponse(myTransaction);
} else {
let myNameRes = await validateName(recipient);
if (myNameRes !== false) {
let myNameAddress = myNameRes.owner
let myTransaction = await makeTransactionRequest(myNameAddress, lastRef)
getTxnRequestResponse(myTransaction)
} else {
console.error(this.renderReceiverText())
this.errorMessage = this.renderReceiverText();
this.sendMoneyLoading = false;
this.btnDisable = false;
}
}
}
const getName = async (recipient)=> {
try {
const getNames = await parentEpml.request("apiCall", {
type: "api",
url: `/names/address/${recipient}`,
});
if (getNames?.length > 0 ) {
return getNames[0].name;
} else {
return '';
}
} catch (error) {
return "";
}
}
const makeTransactionRequest = async (receiver, lastRef) => {
let myReceiver = receiver;
let mylastRef = lastRef;
let dialogamount = get("transactions.amount");
let dialogAddress = get("login.address");
let dialogName = get("login.name");
let dialogto = get("transactions.to");
let recipientName = await getName(myReceiver);
let myTxnrequest = await parentEpml.request('transaction', {
type: 2,
nonce: this.myAddress.nonce,
params: {
recipient: myReceiver,
recipientName: recipientName,
amount: amount,
lastReference: mylastRef,
fee: 0.001,
dialogamount: dialogamount,
dialogto: dialogto,
dialogAddress,
dialogName
},
})
return myTxnrequest;
}
const getTxnRequestResponse = (txnResponse) => {
if (txnResponse.success === false && txnResponse.message) {
this.errorMessage = txnResponse.message;
this.sendMoneyLoading = false;
this.btnDisable = false;
throw new Error(txnResponse);
} else if (txnResponse.success === true && !txnResponse.data.error) {
this.shadowRoot.getElementById('amountInput').value = '';
this.userName = '';
this.errorMessage = '';
this.successMessage = this.renderSuccessText();
this.sendMoneyLoading = false;
this.btnDisable = false;
setTimeout(() => {
this.setOpenTipUser(false);
this.chatEditor.enable();
this.successMessage = "";
}, 3000);
} else {
this.errorMessage = txnResponse.data.message;
this.sendMoneyLoading = false;
this.btnDisable = false;
throw new Error(txnResponse);
}
}
validateReceiver(recipient);
}
render() {
console.log(5, "Tip User Here");
console.log(this.openUserInfo, "openUserInfo here");
return html`
<div class="tip-user-header">
<img src="/img/qort.png" width="32" height="32">
<p class="tip-user-header-font">${translate("grouppage.gchange55")} ${this.userName}</p>
</div>
<div class="tip-user-body">
<p class="tip-available">${translate("grouppage.gchange59")}: ${this.walletBalance} QORT</p>
<input id="amountInput" class="tip-input" type="number" placeholder="${translate("grouppage.gchange58")}" />
<p class="tip-available">${translate("grouppage.gchange61")}: 0.001 QORT</p>
${this.sendMoneyLoading ?
html`
<paper-progress indeterminate style="width: 100%; margin: 4px;">
</paper-progress>`
: html`
<div style=${"text-align: center;"}>
<vaadin-button
?disabled=${this.btnDisable}
theme="primary medium"
style="width: 100%; cursor: pointer"
@click=${() => this.sendQort()}>
<vaadin-icon icon="vaadin:arrow-forward" slot="prefix"></vaadin-icon>
${translate("grouppage.gchange62")} QORT
</vaadin-button>
</div>
`}
${this.successMessage ?
html`
<p class="success-msg">
${this.successMessage}
</p>
`
: this.errorMessage ?
html`
<p class="error-msg">
${this.errorMessage}
</p>
`
: null}
</div>
`;
}
}
customElements.define('tip-user', TipUser);

View File

@ -13,7 +13,7 @@ export const getUserNameFromAddress = async (address) => {
if (Array.isArray(getNames) && getNames.length > 0 ) {
return getNames[0].name;
} else {
return cropAddress(address);
return address;
}
} catch (error) {
console.error(error);