import React, { Component } from "react";
import { IconButton, Paper, Rating, Button, CircularProgress, Grid, Typography, TextField, List, ListItem, ListItemText, Divider, ListItemIcon, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@mui/material"
import QRCode from "react-qr-code";
import Countdown, { zeroPad} from 'react-countdown';
import Chat from "./Chat"
import MediaQuery from 'react-responsive'
import QrReader from 'react-qr-reader'
// Icons
import PercentIcon from '@mui/icons-material/Percent';
import BookIcon from '@mui/icons-material/Book';
import QrCodeScannerIcon from '@mui/icons-material/QrCodeScanner';
import SendIcon from '@mui/icons-material/Send';
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
// pretty numbers
function pn(x) {
var parts = x.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
}
export default class TradeBox extends Component {
constructor(props) {
super(props);
this.state = {
openConfirmFiatReceived: false,
openConfirmDispute: false,
openEnableTelegram: false,
badInvoice: false,
badStatement: false,
qrscanner: false,
}
}
Sound = ({soundFileName}) => (
// Four filenames: "locked-invoice", "taker-found", "open-chat", "sucessful"
)
togglePlay = () => {
this.setState({ playSound: !this.state.playSound }, () => {
this.state.playSound ? this.audio.play() : this.audio.pause();
});
}
handleClickOpenConfirmDispute = () => {
this.setState({openConfirmDispute: true});
};
handleClickCloseConfirmDispute = () => {
this.setState({openConfirmDispute: false});
};
handleClickAgreeDisputeButton=()=>{
const requestOptions = {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
body: JSON.stringify({
'action': "dispute",
}),
};
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
.then((response) => response.json())
.then((data) => this.props.completeSetState(data));
this.handleClickCloseConfirmDispute();
}
ConfirmDisputeDialog =() =>{
return(
)
}
handleClickOpenConfirmFiatReceived = () => {
this.setState({openConfirmFiatReceived: true});
};
handleClickCloseConfirmFiatReceived = () => {
this.setState({openConfirmFiatReceived: false});
};
handleClickTotallyConfirmFiatReceived = () =>{
this.handleClickConfirmButton();
this.handleClickCloseConfirmFiatReceived();
};
ConfirmFiatReceivedDialog =() =>{
return(
)
}
showQRInvoice=()=>{
return (
Robots show commitment to their peers
{this.props.data.is_maker ?
Lock {pn(this.props.data.bond_satoshis)} Sats to PUBLISH order
:
Lock {pn(this.props.data.bond_satoshis)} Sats to TAKE the order
}
);
}
showBondIsLocked=()=>{
return (
π Your {this.props.data.is_maker ? 'maker' : 'taker'} bond is locked
);
}
showBondIsSettled=()=>{
return (
βοΈ Your {this.props.data.is_maker ? 'maker' : 'taker'} bond was settled
);
}
showBondIsReturned=()=>{
return (
π Your {this.props.data.is_maker ? 'maker' : 'taker'} bond was unlocked
);
}
showEscrowQRInvoice=()=>{
return (
{/* Make confirmation sound for HTLC received. */}
Deposit {pn(this.props.data.escrow_satoshis)} Sats as trade collateral
{this.showBondIsLocked()}
);
}
showTakerFound=()=>{
return (
{/* Make bell sound when taker is found */}
A taker has been found!
Please wait for the taker to lock a bond.
If the taker does not lock a bond in time, the order will be made
public again.
{this.showBondIsLocked()}
);
}
handleClickOpenTelegramDialog = () => {
this.setState({openEnableTelegram: true});
};
handleClickCloseEnableTelegramDialog = () => {
this.setState({openEnableTelegram: false});
};
handleClickEnableTelegram = () =>{
window.open("https://t.me/"+this.props.data.tg_bot_name+'?start='+this.props.data.tg_token, '_blank').focus()
this.handleClickCloseEnableTelegramDialog();
};
EnableTelegramDialog =() =>{
return(
)
}
showMakerWait=()=>{
return (
{/* Make confirmation sound for HTLC received. */}
Your order is public. Wait for a taker.
Be patient while robots check the book.
It might take some time. This box will ring π once a robot takes your order.
Please note that if your premium is excessive, or your currency or payment
methods are not popular, your order might expire untaken. Your bond will
return to you (no action needed).
{this.props.data.tg_enabled ?
Telegram enabled
:
}
{/* TODO API sends data for a more confortable wait */}
{this.showBondIsLocked()}
)
}
handleInputInvoiceChanged=(e)=>{
this.setState({
invoice: e.target.value,
badInvoice: false,
});
}
handleClickSubmitInvoiceButton=()=>{
this.setState({badInvoice:false});
const requestOptions = {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
body: JSON.stringify({
'action':'update_invoice',
'invoice': this.state.invoice,
}),
};
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
.then((response) => response.json())
.then((data) => this.setState({badInvoice:data.bad_invoice})
& this.props.completeSetState(data));
}
handleInputDisputeChanged=(e)=>{
this.setState({
statement: e.target.value,
badStatement: false,
});
}
handleClickSubmitStatementButton=()=>{
this.setState({badInvoice:false});
const requestOptions = {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
body: JSON.stringify({
'action':'submit_statement',
'statement': this.state.statement,
}),
};
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
.then((response) => response.json())
.then((data) => this.setState({badStatement:data.bad_statement})
& this.props.completeSetState(data));
}
handleScan = data => {
if (data) {
this.setState({
invoice: data
})
}
}
handleError = err => {
console.error(err)
}
handleQRbutton = () => {
this.setState({qrscanner: !this.state.qrscanner});
}
showInputInvoice(){
return (
{/* Make confirmation sound for HTLC received. */}
Submit a LN invoice for {pn(this.props.data.invoice_amount)} Sats
The taker is committed! Before letting you send {" "+ parseFloat(parseFloat(this.props.data.amount).toFixed(4))+
" "+ this.props.data.currencyCode}, we want to make sure you are able to receive the BTC. Please provide a
valid invoice for {pn(this.props.data.invoice_amount)} Satoshis.
{this.state.qrscanner ?
: null }
{this.showBondIsLocked()}
)
}
// Asks the user for a dispute statement.
showInDisputeStatement=()=>{
if(this.props.data.statement_submitted){
return (
We have received your statement
We are waiting for your trade counterparty statement. If you are hesitant about
the state of the dispute or want to add more information, contact robosats@protonmail.com.
Please, save the information needed to identificate your order and your payments: order ID;
payment hashes of the bonds or escrow (check on your lightning wallet); exact amount of
satoshis; and robot nickname. You will have to identify yourself as the user involved
in this trade via email (or other contact methods).
{this.showBondIsSettled()}
)
}else{
return (
// TODO Option to upload files
A dispute has been opened
Please, submit your statement. Be clear and specific about what happened and provide the necessary
evidence. You MUST provide a contact method: burner email, XMPP or telegram username to follow up with the staff.
Disputes are solved at the discretion of real robots (aka humans), so be as helpful
as possible to ensure a fair outcome. Max 5000 chars.
{this.showBondIsSettled()}
)}
}
showWaitForDisputeResolution=()=>{
return (
We have the statements
Both statements have been received, wait for the staff to resolve the dispute.
The dispute winner will be asked to submit a LN invoice via the contact methods provided.
If you are hesitant about the state of the dispute or want to add more information,
contact robosats@protonmail.com. If you did not provide a contact method, write us inmediately.
Please, save the information needed to identificate your order and your payments: order ID;
payment hashes of the bonds or escrow (check on your lightning wallet); exact amount of
satoshis; and robot nickname. You will have to identify yourself as the user involved
in this trade via email (or other contact methods).
{this.showBondIsSettled()}
)
}
showDisputeWinner=()=>{
return (
You have won the dispute
You will be sent the satoshis of the escrow and your fidelity bond.
This is not an automatic process, instead it will be sent manually by the staff.
Please coordinate with the staff by writing to robosats@protonmail.com (or via your provided
burner contact method). You will be asked to submit a new invoice together with identificative
information about this order (bond payment hash, robot nicknames, exact amount in satoshis and order ID).
{this.showBondIsSettled()}
)
}
showDisputeLoser=()=>{
return (
You have lost the dispute
Unfortunately you have lost the dispute. If you think this is a mistake
you can ask to re-open the case via email to robosats@protonmail.com. However,
chances of it being investigated again are low.
{this.showBondIsSettled()}
)
}
showWaitingForEscrow(){
return(
Your invoice looks good!π
We are waiting for the seller lock the trade amount.
Just hang on for a moment. If the seller does not deposit,
you will get your bond back automatically.
{this.showBondIsLocked()}
)
}
showWaitingForBuyerInvoice(){
return(
{/* Make confirmation sound for HTLC received. */}
The trade collateral is locked! π
We are waiting for the buyer to post a lightning invoice. Once
he does, you will be able to directly communicate the fiat payment
details.
Just hang on for a moment. If the buyer does not cooperate,
you will get back the trade collateral and your bond automatically.
{this.showBondIsLocked()}
)
}
handleClickConfirmButton=()=>{
const requestOptions = {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
body: JSON.stringify({
'action': "confirm",
}),
};
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
.then((response) => response.json())
.then((data) => this.props.completeSetState(data));
}
handleRatingUserChange=(e)=>{
const requestOptions = {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
body: JSON.stringify({
'action': "rate_user",
'rating': e.target.value,
}),
};
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
.then((response) => response.json())
.then((data) => this.props.completeSetState(data));
}
handleRatingRobosatsChange=(e)=>{
if (this.state.rating_platform != null){
return null
}
this.setState({rating_platform:e.target.value});
const requestOptions = {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
body: JSON.stringify({
'action': "rate_platform",
'rating': e.target.value,
}),
};
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
.then((response) => response.json())
.then((data) => this.props.completeSetState(data));
}
showFiatSentButton(){
return(
)
}
showFiatReceivedButton(){
return(
)
}
showOpenDisputeButton(){
// TODO, show alert about how opening a dispute might involve giving away personal data and might mean losing the bond. Ask for double confirmation.
return(
)
}
showOrderExpired(){
return(
The order has expired
)
}
showChat=()=>{
//In Chatroom - No fiat sent - showChat(showSendButton, showReveiceButton, showDisputeButton)
if(this.props.data.is_buyer & this.props.data.status == 9){
var showSendButton=true;
var showReveiceButton=false;
var showDisputeButton=true;
}
if(this.props.data.is_seller & this.props.data.status == 9){
var showSendButton=false;
var showReveiceButton=false;
var showDisputeButton=true;
}
//In Chatroom - Fiat sent - showChat(showSendButton, showReveiceButton, showDisputeButton)
if(this.props.data.is_buyer & this.props.data.status == 10){
var showSendButton=false;
var showReveiceButton=false;
var showDisputeButton=true;
}
if(this.props.data.is_seller & this.props.data.status == 10){
var showSendButton=false;
var showReveiceButton=true;
var showDisputeButton=true;
}
return(
{/* Make confirmation sound for Chat Open. */}
Chatting with {this.props.data.is_maker ? this.props.data.taker_nick : this.props.data.maker_nick}
{this.props.data.is_seller ?
Say hi! Be helpful and concise. Let them know how to send you {this.props.data.currencyCode}.
:
Say hi! Ask for payment details and click "Confirm Sent" as soon as the payment is sent.
}
{showDisputeButton ? this.showOpenDisputeButton() : ""}
{showSendButton ? this.showFiatSentButton() : ""}
{showReveiceButton ? this.showFiatReceivedButton() : ""}
{this.showBondIsLocked()}
)
}
showRateSelect(){
return(
{/* Make confirmation sound for Chat Open. */}
πTrade finished!π₯³
What do you think of β‘{this.props.data.is_maker ? this.props.data.taker_nick : this.props.data.maker_nick}β‘?
What do you think of π€RoboSatsπ€?
{this.state.rating_platform==5 ?
Thank you! RoboSats loves you too β€οΈ
RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!
: null}
{this.state.rating_platform!=5 & this.state.rating_platform!=null ?
Thank you for using Robosats! Let us know what you did not like and how the platform could improve
(Telegram / Github)
: null}
{this.showBondIsReturned()}
)
}
showSendingPayment(){
return(
Attempting Lightning Payment
RoboSats is trying to pay your lightning invoice. Remember that lightning nodes must
be online in order to receive payments.
)
}
// Countdown Renderer callback with condition
countdownRenderer = ({ minutes, seconds, completed }) => {
if (completed) {
// Render a completed state
return (
Retrying!
);
} else {
return (
{zeroPad(minutes)}m {zeroPad(seconds)}s
);
}
};
showRoutingFailed=()=>{
// TODO If it has failed 3 times, ask for a new invoice.
if(this.props.data.invoice_expired){
return(
Lightning Routing Failed
Your invoice has expires or more than 3 payments attempts have been made.
Submit a LN invoice for {pn(this.props.data.invoice_amount)} Sats
{this.showBondIsReturned()}
)
}else{
return(
Lightning Routing Failed
RoboSats will try to pay your invoice 3 times every 5 minutes. If it keeps failing, you
will be able to submit a new invoice. Check whether you have enough inboud liquidity.
Remember that lightning nodes must be online in order to receive payments.
{this.showBondIsReturned()}
)}
}
render() {
return (
Contract Box
{/* Maker and taker Bond request */}
{this.props.data.is_maker & this.props.data.status == 0 ? this.showQRInvoice() : ""}
{this.props.data.is_taker & this.props.data.status == 3 ? this.showQRInvoice() : ""}
{/* Waiting for taker and taker bond request */}
{this.props.data.is_maker & this.props.data.status == 1 ? this.showMakerWait() : ""}
{this.props.data.is_maker & this.props.data.status == 3 ? this.showTakerFound() : ""}
{/* Send Invoice (buyer) and deposit collateral (seller) */}
{this.props.data.is_seller & (this.props.data.status == 6 || this.props.data.status == 7 ) ? this.showEscrowQRInvoice() : ""}
{this.props.data.is_buyer & (this.props.data.status == 6 || this.props.data.status == 8 )? this.showInputInvoice() : ""}
{this.props.data.is_buyer & this.props.data.status == 7 ? this.showWaitingForEscrow() : ""}
{this.props.data.is_seller & this.props.data.status == 8 ? this.showWaitingForBuyerInvoice() : ""}
{/* In Chatroom */}
{this.props.data.status == 9 || this.props.data.status == 10 ? this.showChat(): ""}
{/* Trade Finished */}
{(this.props.data.is_seller & [13,14,15].includes(this.props.data.status)) ? this.showRateSelect() : ""}
{(this.props.data.is_buyer & this.props.data.status == 14) ? this.showRateSelect() : ""}
{/* Trade Finished - Payment Routing Failed */}
{this.props.data.is_buyer & this.props.data.status == 13 ? this.showSendingPayment() : ""}
{/* Trade Finished - Payment Routing Failed */}
{this.props.data.is_buyer & this.props.data.status == 15 ? this.showRoutingFailed() : ""}
{/* Trade Finished - TODO Needs more planning */}
{this.props.data.status == 11 ? this.showInDisputeStatement() : ""}
{this.props.data.status == 16 ? this.showWaitForDisputeResolution() : ""}
{(this.props.data.status == 17 & this.props.data.is_taker) || (this.props.data.status == 18 & this.props.data.is_maker) ? this.showDisputeWinner() : ""}
{(this.props.data.status == 18 & this.props.data.is_taker) || (this.props.data.status == 17 & this.props.data.is_maker) ? this.showDisputeLoser() : ""}
{/* Order has expired */}
{this.props.data.status == 5 ? this.showOrderExpired() : ""}
{/* TODO */}
{/* */}
{/* */}
);
}
}