SteemAX 1.3 ~ Improved GUI With Pending Invite Counter & Ajax

Repository

https://github.com/artolabs/steemax

steemax_update_1.3.png

SteemAX helps minnow content-creators and whale curators

by automating an exchange of upvotes between their quality blog posts, allowing both a 1 to 1 as well as disproportional exchanges that grant bigger curations, better support than a bid bot, and the long-term support they both deserve.

SteemAX 1.3 Improvements

Since launching SteemAX (Beta) in August a number of users have provided contextual feedback so that some much needed improvements could be made to the user interface. Of these, the biggest change is the user flow for creating invites. Instead of directing the user to SteemAX.info after creating an invite, the user does not leave the invite creation form and instead sees a notification bubble appear on the "My Invitation" button indicating the number of invites that require action. These actions include sending newly created invites as well as accepting newly received invites from other users. This enables the user to create multiple invites without having to click the back button or re-login, and it gives them an immediate notice that they've received invites when they first log in.

The header of all SteemAX pages were given a face lift too, giving SteemAX a more appealing look and easier-to-grasp design.

Commit 541159a Commit e218764 Commit da3d245 Commit 9202721 Commit 33e3a51

Also, the wording on the submit button was changed from "Invite to Exchange" to "Create Invite" as this wording makes it clearer that additional action is needed, e.g. the invite has not yet been sent, it's only been created. Finally, the account names are now displayed next to their vote values so it's easier to understand which is which.

invite_counter.gif

AJAX

To accomplish the new user flow, AJAX was used. It first submits the data then waits for success, otherwise the returned message is considered an error and is displayed to the user.

var httpObject;
function addInvite() {
    /* first we filter the input given by the user */
    var regex = new RegExp('[^0-9\\.]', 'g');
    var per = document.getElementById("percentage").value.replace(regex, '');
    var ratio = document.getElementById("ratio").value.replace(regex, '');
    var dur = document.getElementById("duration").value.replace(regex, '');
    var regex = new RegExp('[^A-Za-z0-9\\.\\-\\_]', 'g');
    var acctname = document.getElementById("accountbox").value.replace(regex, '');
    var code = document.getElementById("code").value.replace(regex, '');
    var captcha = document.getElementById("g-recaptcha-response").value.replace(regex, '');

    /* now we make the url and encode it */
    var url = "https://www.steemax.trade/post.py?code=" + code + "&account=" + acctname + "&percentage=" + per + "&ratio=" + ratio + "&duration=" + dur + "&g-recaptcha-response=" + captcha + "&ajax=1";
    url = encodeURI(url);

    /* create the ajax object */
    httpObject = loadAddInviteAJAX();
    getDynamicData(url);
}
function loadAddInviteAJAX() {
        var xhr_object = null;
        if(window.XMLHttpRequest) {xhr_object = new XMLHttpRequest();} 
        else if(window.ActiveXObject) {xhr_object = new ActiveXObject("Microsoft.XMLHTTP");} 
        else {alert("Your browser doesn't provide XMLHttprequest functionality"); return;}
        return xhr_object;
}
function getDynamicData(url) {
        httpObject.open('GET', url, true);
        httpObject.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        httpObject.onreadystatechange = callbackAddInviteFunction;
        httpObject.send(null);
}
function callbackAddInviteFunction() {
        if (httpObject.readyState != 4) {return 0;}
        else {
        /* When the ajax callback is ready we get the 
        returned result and filter it */
            var response = httpObject.responseText;
            var regex = new RegExp('[^0-9]', 'g');
            if (httpObject.status==200) {
            /* If there's no HTML error and the returned result
            is a single number "1" then we increment the 
            notification bubble. Otherwise we alert the user of 
            the error. */
                if (response.replace(regex, '') === "1") {
                    addNotice(); 
                }
                else {
                    alert(response);
                    stopLoaderGif();
                }
            }
            else {
                alert(response);
                stopLoaderGif();
            }
        }
}

Commit 83753c8

Error Handling

The back-end Python code was updated to look for an ajax flag, which if set to 1 the returned results will be either the number "1" for success, otherwise it will be the error message that will be shown to the user as an alert. If the flag is not a 1 then the returned results default to HTML. This is to ensure that if someone attempts to submit the form without javascript the code will default to HTML and they will at least be taken to the exchange invitation page and shown their new invitation.

Additionally, better error handling was added to ensure that the values for percentage, ratio and duration are within valid limits and the percentage and ratio are checked against the vote values of two accounts to verify that the ratio is valid (see below).

    def invite(self, token, account2, per,
               ratio, dur, response, ip, ajax):
        """ Creates an invite. First we filter the users
        data then we create the invite in the database.
        If the request was sent via ajax then the returned
        result is simply the number 1 to indicate success.
        """
        ajax = sec.filter_number(int(ajax))
        response = sec.filter_token(response)
        per = sec.filter_number(per)
        ratio = sec.filter_number(ratio, 1000)
        dur = sec.filter_number(dur, 365)
        if not self.verify_recaptcha(response, ip):
            return self.error_page("Invalid captcha.", ajax)
        if float(per) < 1 or float(per) > 100:
           return self.error_page("The percentage can not be lower then "
                                + "1 or greater than 100 and must be a whole number.", ajax)    
        if float(dur) < 7 or float(dur) > 365:
           return self.error_page("The duration must be between 7 and 365 days.", ajax)
        if float(ratio) < 0.001 or float(ratio) > 1000:
           return self.error_page("The ratio must be between 0.001 and 1000 to 1.", ajax)
        if self.verify_token(sec.filter_token(token)):
            account2 = sec.filter_account(account2)
            if self.steem.account(account2) is False:
                return self.error_page(account2 
                    + " is an invalid account name.", ajax)
            else:
                verify = axverify.AXverify()
                if verify.eligible_votes(self.steem.username, account2, per, ratio, 1) is False:
                    return self.error_page(verify.response, ajax)
                memoid = self.db.add_invite(self.steem.username, account2, per, ratio, dur)
                if memoid is False:
                    return self.error_page(self.db.errmsg, ajax)
                elif int(memoid) > 0:
                    if (int(ajax) == 1):
                        return "\r\n1"
                    else:
                        return ("Location: https://steemax.info/@"
                                + self.steem.username + "\r\n")
        else:
            return self.auth_url()

    def error_page(self, msg, ajax):
        """ Returns the HTML page with the
        given error message
        """
        if (int(ajax) == 1):
            return "\r\n"+msg
        else:
            msg += ("<br><br>You may be receiving this error in this format "
                + "because you have disabled javascript. Please enable javascript "
                + "for a better user experience.")
            return ("\r\n"
                    + self.make_page(
                        self.load_template("templates/error.html"),
                        ERRORMSG=msg))

Commit 8dd40f0 Commit 01c046a

Notification Bubble

When the AJAX callback function returns success, the addNotice function is used to update the notification bubble counter and trigger the animation class animating. The function showInviteCount is used once at page load to initially trigger the notice animation and show current pending invites when the user first logs in. The removeAnimation function waits 1 second then removes the animation class so it can be triggered again later.

function showInviteCount() {
    ele = document.querySelector('.notice-bubble');
    var num = parseInt(ele.innerHTML);
    if (num > 0) { 
        ele.style.display = "block";
    }
    ele.classList.add('animating');
    removeAnimation();
}
function addNotice() {
    resetForm();
    stopLoaderGif();
    ele = document.querySelector('.notice-bubble');
    var num = parseInt(ele.innerHTML);
    if (num === 0) { 
        ele.style.display = "block";
    }
    ele.innerHTML = num + 1;
    ele.classList.add('animating');
    removeAnimation();
}
function removeAnimation(){
    setTimeout(function() {
        ele = document.querySelector('.notice-bubble');
        ele.classList.remove('animating');
    }, 1000);           
}

Commit 83753c8 Commit 9202721

Improved Ratio Slider

The ratio slider has been given greater granularity so that it's easier to find a more precise ratio. It now has three zones that provide different granularity. When shifted to the far left it's range is from 0.01 to 1, stepping 0.01 at a time. This range allows disproportional exchanges that favor the inviter. Since only minnows will be using this range it has the finest granularity. In the middle zone, the granularity is 0.1, ranging from 1.0 to 9.9. This range is for whales or dolphins creating exchanges with disproportional ratios that favor the invitee, but only slightly. This is the range that has been added as it was found that trying to find the best ratio at this range couldn't be achieved with whole integers. The third zone of the slider, when shifted to the far right, is in whole integers from 10 to 100. This range is for very disparate ratios and don't require much granularity.

<input type="range" min="1" max="300" step="1" name="ratio-slider" id="ratio-slider" class="slider" value="100">


    if (myEvt.target.value > 210) { ratio = myEvt.target.value - 200; }
    else if ((myEvt.target.value <= 210) && (myEvt.target.value > 110)) { 
        ratio = parseFloat((myEvt.target.value - 110) * 0.1).toFixed(1); 
    }
    else if ((myEvt.target.value <= 110) && (myEvt.target.value > 10)) {
      // Lowest ratio is 0.01:1
        ratio = (myEvt.target.value - 10) / 100; 
    }

Commit 27bb140

Fixed Invalid Ratio Issue

An invalid ratio occurs when a user with a large upvote value attempts to invite a user with a low upvote value into an impossible exchange. This happens because the Steem blockchain does not allow vote weights at a fraction of a percent, therefore a vote value cannot be lower than 1%. For instance, a user with a vote value of $6.00 can only vote as low as $0.06. If this user attempts to invite another user with a vote value of only $0.01, they cannot achieve a 1:1 ratio, and the inviter must choose a disproportional ratio (at least 6:1) if they wish to create an exchange.

ratio_slider.gif

Issue

It was discovered that although the invite creation form and the barter pop-up form informed the user that they had chosen an invalid ratio, the form could still be submitted.

Issue #10

Fix: This was fixed by adding a new variable to the javascript that tracks the state of the ratio slider. When the form is submitted this variable is checked and if the ratio is invalid an error is generated and the submission canceled. The Python backend was updated as well by giving it better error handling and by checking the vote values even after the form has been submitted (see above) to ensure the user cannot bypass error checking by turning off javascript.

var invalidratio = 0;

    *
    *
    else if (invalidratio === 1) {
        input_error(document.getElementById("ratio"));
        alert("Please enter a valid ratio.");
    }

Commit 27bb140 Commit 33e3a51

Technology Stack

SteemAX is written to use Python 3.5 and MySQL. The web interface for https://steemax.trade and https://steemax.info has been written in HTML, CSS and Javascript.

Roadmap

In the future more contributors will be brought into the fold
via Task Requests to help improve the functionality of the site and most especially the look and feel. After all, projects always benefit from the synergistic action of teamwork.

Contact

Please contact Mike (Mike-A) on Discord
https://discord.gg/97GKVFC

GitHub Account

https://github.com/artolabs

Sort:  

Thanks for the contribution, @learnelectronics! It looks like a great deal of work went into this and the added features are great as well - keep it up! Especially the improvement of the user flow for creating invites makes a lot of sense to me; it's a great quality of life improvement.

Your commit messages have definitely improved as well. They are a lot more concise and descriptive, so also great job on that. As for the JavaScript code I think it's better to use let and const instead of var and I would also recommend using a linter like ESLint and maybe also a code formatter like prettier as there were some minor inconsistencies that could be prevented with them.

Overall a great job and I look forward to seeing your next contribution!


Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Thank you for your review, @amosbastian!

So far this week you've reviewed 21 contributions. Keep up the good work!

Thank you learnelectronics! You've just received an upvote of 48% by @ArtTurtle!


Learn how I will upvote each and every one of your art and music posts

Hi @learnelectronics!

Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server

I think there is a bug. I voted for 8 days old post.

ax1.png

ax.png

Sorry, didn't see this til now. I'll look into it immediately. Can you contact me on discord?

OK, found the bug and fixed it! Thank you!

Hey, @learnelectronics!

Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!