Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New As a user, I would like to be able to request money from someon, so I can get paid and remind a forgetful funder. #6411

Merged
merged 15 commits into from
Apr 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@
url(r'^tip/send/?', dashboard.tip_views.send_tip, name='send_tip'),
url(r'^send/?', dashboard.tip_views.send_tip, name='tip'),
url(r'^tip/?', dashboard.tip_views.send_tip_2, name='tip'),

url(r'^requestmoney/?', dashboard.tip_views.request_money, name='request_money'),
# Legal
re_path(r'^terms/?', dashboard.views.terms, name='_terms'),
re_path(r'^legal/terms/?', dashboard.views.terms, name='terms'),
Expand Down
98 changes: 98 additions & 0 deletions app/assets/onepager/js/request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
$(document).ready(function() {
$('#network').change(function(e) {
if ($(this).val() !== 'ETH') {
$('#token').prop('disabled', 'disabled');
$('#token').val('');
} else {
$('#token').prop('disabled', false);
}
});
$('#request').on('click', function(e) {
e.preventDefault();
if ($(this).hasClass('disabled'))
return;

if (!$('#tos').is(':checked')) {
_alert('Please accept the terms and conditions before submit.', 'warning');
}
loading_button($(this));
// get form data
const username = $('.username-search').select2('data')[0] ? $('.username-search').select2('data')[0].text : '';
const amount = parseFloat($('#amount').val());
const network = $('#network').val();
const address = $('#address').val();
const comments = $('#comments').val();
const tokenAddress = (
($('#token').val() == '0x0') ?
'0x0000000000000000000000000000000000000000'
: $('#token').val());


// derived info
const isSendingETH = (tokenAddress == '0x0' || tokenAddress == '0x0000000000000000000000000000000000000000');
const tokenDetails = tokenAddressToDetails(tokenAddress);
let tokenName;

if (network == 'ETH' == !isSendingETH) {
tokenName = tokenDetails.name;
} else {
tokenName = 'ETH';
}

if (!username) {
_alert('Please enter a recipient', 'error');
return;
}

const success_callback = function() {
unloading_button($('#request'));
};
const failure_callback = function() {
unloading_button($('#request'));
};

return requestFunds(username, amount, comments, tokenAddress, tokenName, network, address, success_callback, failure_callback);

});

});

function requestFunds(username, amount, comments, tokenAddress, tokenName, network, address, success_callback, failure_callback) {
if (username.indexOf('@') == -1) {
username = '@' + username;
}

if (!isNumeric(amount) || amount == 0) {
_alert({ message: gettext('You must enter the amount!') }, 'warning');
failure_callback();
return;
}
if (username == '') {
_alert({ message: gettext('You must enter a username.') }, 'warning');
failure_callback();
return;
}

const csrfmiddlewaretoken = $('[name=csrfmiddlewaretoken]').val();
const url = '/requestmoney';
const formData = new FormData();

formData.append('username', username);
formData.append('amount', amount);
formData.append('tokenName', tokenName);
formData.append('comments', comments);
formData.append('tokenAddress', tokenAddress);
formData.append('csrfmiddlewaretoken', csrfmiddlewaretoken);
formData.append('network', network);
formData.append('address', address);
fetch(url, {
method: 'POST',
body: formData
}).then(function(json) {
_alert('The funder has been notified', 'success');
success_callback();
}).catch(function(error) {
_alert('Something goes wrong, try later.', 'error');
failure_callback();
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ could we update all var to const/let?

3 changes: 2 additions & 1 deletion app/assets/onepager/js/send.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ $(document).ready(function() {
waitforWeb3(function() {
tokens(document.web3network).forEach(function(ele) {
if (ele && ele.addr) {
var html = '<option value=' + ele.addr + '>' + ele.name + '</option>';
const is_token_selected = $('#token').data('token') === ele.name ? ' selected' : ' ';
const html = '<option value=' + ele.addr + is_token_selected + '>' + ele.name + '</option>';

$('#token').append(html);
}
Expand Down
13 changes: 11 additions & 2 deletions app/dashboard/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
CoinRedemption, CoinRedemptionRequest, Coupon, Earning, FeedbackEntry, HackathonEvent, HackathonProject,
HackathonRegistration, HackathonSponsor, Interest, LabsResearch, PortfolioItem, Profile, ProfileView,
SearchHistory, Sponsor, Tip, TipPayout, TokenApproval, Tool, ToolVote, TribeMember, UserAction,
UserVerificationModel, Poll, Question, Option, Answer
UserVerificationModel, Poll, Question, Option, Answer, FundRequest
)


Expand Down Expand Up @@ -411,6 +411,14 @@ class TribeMemberAdmin(admin.ModelAdmin):
list_display = ['pk', 'profile', 'org', 'leader', 'status']


class FundRequestAdmin(admin.ModelAdmin):
list_display = ['id', 'profile', 'requester', 'network', 'token_name', 'amount',
'comments', 'address', 'tip', 'created_on']
readonly_fields = ['id']
ordering = ['-id']
raw_id_fields = ['profile', 'requester', 'tip']


class QuestionInline(admin.TabularInline):
fields = ['id', 'poll', 'question_type', 'text']
readonly_fields = ['id']
Expand Down Expand Up @@ -487,7 +495,8 @@ class AnswersAdmin(admin.ModelAdmin):
admin.site.register(UserVerificationModel, VerificationAdmin)
admin.site.register(Coupon, CouponAdmin)
admin.site.register(TribeMember, TribeMemberAdmin)
admin.site.register(FundRequest, FundRequestAdmin)
admin.site.register(Poll, PollsAdmin)
admin.site.register(Question, QuestionsAdmin)
admin.site.register(Option, OptionsAdmin)
admin.site.register(Answer, AnswersAdmin)
admin.site.register(Answer, AnswersAdmin)
34 changes: 34 additions & 0 deletions app/dashboard/migrations/0104_fundrequest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 2.2.4 on 2020-04-29 14:38

from django.db import migrations, models
import django.db.models.deletion
import economy.models


class Migration(migrations.Migration):

dependencies = [
('dashboard', '0103_auto_20200428_1057'),
]

operations = [
migrations.CreateModel(
name='FundRequest',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('modified_on', models.DateTimeField(default=economy.models.get_time)),
('token_name', models.CharField(default='ETH', max_length=255)),
('amount', models.DecimalField(decimal_places=4, default=1, max_digits=50)),
('comments', models.TextField(blank=True, default='')),
('network', models.CharField(default='', max_length=255)),
('address', models.CharField(default='', max_length=255)),
('created_on', models.DateTimeField(auto_now_add=True)),
('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='requests_receiver', to='dashboard.Profile')),
('requester', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='requests_sender', to='dashboard.Profile')),
('tip', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='dashboard.Tip')),
],
options={
'abstract': False,
},
),
]
24 changes: 23 additions & 1 deletion app/dashboard/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
_AUTH, HEADERS, TOKEN_URL, build_auth_dict, get_gh_issue_details, get_issue_comments, issue_number, org_name,
repo_name,
)
from marketing.mails import featured_funded_bounty, start_work_approved
from marketing.mails import featured_funded_bounty, fund_request_email, start_work_approved
from marketing.models import LeaderboardRank
from rest_framework import serializers
from web3 import Web3
Expand Down Expand Up @@ -1740,6 +1740,28 @@ def __str__(self):
return f"tip: {self.tip.pk} profile: {self.profile.handle}"


class FundRequest(SuperModel):
profile = models.ForeignKey(
'dashboard.Profile', related_name='requests_receiver', on_delete=models.CASCADE
)
requester = models.ForeignKey(
'dashboard.Profile', related_name='requests_sender', on_delete=models.CASCADE
)
token_name = models.CharField(max_length=255, default='ETH')
amount = models.DecimalField(default=1, decimal_places=4, max_digits=50)
comments = models.TextField(default='', blank=True)
tip = models.OneToOneField(Tip, on_delete=models.CASCADE, null=True)
network = models.CharField(max_length=255, default='')
address = models.CharField(max_length=255, default='')
created_on = models.DateTimeField(auto_now_add=True)


@receiver(post_save, sender=FundRequest, dispatch_uid="post_save_fund_request")
def psave_fund_request(sender, instance, created, **kwargs):
if created:
fund_request_email(instance, [instance.profile.email])


@receiver(pre_save, sender=Tip, dispatch_uid="psave_tip")
def psave_tip(sender, instance, **kwargs):
# when a new tip is saved, make sure it doesnt have whitespace in it
Expand Down
16 changes: 11 additions & 5 deletions app/dashboard/templates/onepager/send2.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,24 @@ <h1>{% trans "Send Tip." %}</h1>
<div>
<br>
{% trans "Amount of" %}
<select id="token" style="width:100px; margin-bottom: 10px; display: inline;" >
<option value="0x0">ETH</option>
</select>
<input name="amount" type="text" placeholder="1.1" id="amount" value="" style="width: 170px; display: inline;">
{% if fund_request.network == 'ETH' %}
<select id="token" style="width:100px; margin-bottom: 10px; display: inline;" data-token="{{ fund_request.token_name }}">
<option value="0x0">ETH</option>
</select>
{% else %}
<select style="width:100px; margin-bottom: 10px; display: inline;">
<option value="{{ fund_request.network }}">{{ fund_request.network }}</option>
</select>
Comment on lines +88 to +90
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @zoek1 just to let you know here was missing the id="token" so the token select wasn't getting fill.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @octavioamu, I'm not sure why the id was missing. I really tested 😧

{% endif %}
<input name="amount" type="text" placeholder="1.1" id="amount" value="{{ fund_request.amount }}" style="width: 170px; display: inline;">
</div>
<div id="usd_amount"> &nbsp;</div>
<div id="tooltip" class="ethinfo-hover">{% trans "Where is my Eth going? " %}<i class='fa fa-info-circle'></i></div><br>
<div class="pb-1 to_name">
<label>{% trans "To Github Username" %}:</label> <br>
<select id="username" class="username-search custom-select" style="max-width: 400px; margin-left: auto; margin-right: auto;">
{% if user_json %}
<option value="{{ user_json.id }}" avatar_id="{{ user_json.avatar_id }}" avatar_url="{{ user_json.avatar_url }}" preferred_payout_address="{{ user_json.preferred_payout_address }}">{{ user_json.text }}</option>
<option value="{{ user_json.id }}" avatar_id="{{ user_json.avatar_id }}" avatar_url="{{ user_json.avatar_url }}" preferred_payout_address="{{ fund_request.address|default:user_json.preferred_payout_address }}">{{ user_json.text }}</option>
{% endif %}
</select>
</div>
Expand Down
137 changes: 137 additions & 0 deletions app/dashboard/templates/request_payment.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
{% extends 'onepager/base.html' %}
{% comment %}
Copyright (C) 2020 Gitcoin Core

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
{% endcomment %}
{% load i18n static %}
{% block 'scripts' %}
{% include 'shared/current_profile.html' %}
<script src="{% static "v2/js/lib/ipfs-api.js" %}"></script>
<script src="{% static "v2/js/ipfs.js" %}"></script>
<script src="{% static "v2/js/tokens.js" %}"></script>
<script src="{% static "v2/js/abi.js" %}"></script>
<script src="{% static "v2/js/amounts.js" %}"></script>
<script src="{% static "v2/js/lib/secrets.min.js" %}"></script>
<script src="{% static "v2/js/ethereumjs-accounts.js" %}"></script>
<script src="/dynamic/js/tokens_dynamic.js"></script>
<script src="{% static "onepager/js/send.js" %}"></script>
<script src="{% static "onepager/js/request.js" %}"></script>
<script src="{% static "onepager/js/confetti.js" %}"></script>
<script src="{% static "v2/js/user-search.js" %}"></script>
<script src="{% static "v2/js/tooltip_hover.js" %}"></script>
{% endblock %}
<!-- Main -->
{% block 'main' %}
<link rel="stylesheet" type="text/css" href="{% static "v2/css/tooltip_hover.css" %}">
<style>
#ethinfo-tooltip {
top: 19.5rem;
left: 25rem;
max-width: 80%;
z-index: 10000;
}

@media only screen and (max-width: 1050px) {
#ethinfo-tooltip {
top: 21rem;
left: 3.5rem;
}
}

.send2 .navbar-network {
padding: 3px;
}

.pb-1 {
padding-bottom: 1rem;
}

#fromName,
#advanced_toggle,
.to_name .select2-container,
.terms {
margin-top: 0.75rem;
}

.amount > span {
margin: 0;
width: 100% !important;
}
</style>
<div>
<canvas id="world" style="position:fixed; top:0px; left: 0px;"></canvas>
</div>
<section id="main" style="padding-top: 1.5rem; margin-top: 1.5rem;">
<header>
<span class="avatar">
<a href="{% url "request_money" %}">
<img src="{% static "v2/images/helmet.png" %}" style="background-color: white; max-width: 100px; max-height: 100px;" alt="Helmet" />
</a>
</span>
</header>
<div id="send_eth">
<h1>{% trans "Request money." %}</h1>
{% csrf_token %}
<div>
<div class="pb-1 my-3 to_name">
<label class="mb-3 ">{% trans "To Github Username" %}</label>
<select id="username" class="username-search custom-select" style="max-width: 400px; margin-left: auto; margin-right: auto;">
{% if user_json %}
<option value="{{ user_json.id }}" avatar_id="{{ user_json.avatar_id }}" avatar_url="{{ user_json.avatar_url }}" preferred_payout_address="{{ user_json.preferred_payout_address }}">{{ user_json.text }}</option>
{% endif %}
</select>
</div>
<div class="my-3 pb-1 network d-flex align-items-center">
<label class="my-0 pl-0 col-3 text-left" style="height: 100%">{% trans "Network" %}</label>
<select id="network" class="mt-1 custom-select">
<option value="ETH">Ethereum</option>
<option value="ETC">ETC</option>
<option value="BCH">Bitcoin Cash</option>
<option value="BTC">Bitcoin</option>
<option value="SIA">Sia</option>
</select>
</div>
<div class="my-3 pb-1 amount d-flex align-items-center justify-content-between">
<label class="my-0 pl-0 col-3 text-left" style="height: 100%">{% trans "Amount of" %}</label>
<select class="col-4" id="token" class="mt-1 custom-select">
<option value="0x0">ETH</option>
</select>
<input class="ml-3 col-4" name="amount" type="text" placeholder="1.1" id="amount" value="">
</div>
<div class="pb-1 my-3 address">
<label>{% trans "Address" %}:</label> <br>
<input name="address" type="text" id="address" value="">
</div>
<div class="my-2 comments">
{% trans "Comments" %}:
<textarea id="comments"></textarea>
</div>
<div class="terms pb-1">
<input type="checkbox" id="tos" value="1" >
<label for="tos">{% blocktrans %}I understand &amp; agree to the <a href="https://gitcoin.co/terms">terms of service</a>.{% endblocktrans %}</label>
</div>
<a href="#" id="request" class="button button--primary">{% trans "Send" %} <span class="emoji">⚡️</span></a>
</div>
</div>
</section>

{% if not user_json and username %}
<script>
setTimeout(function() {
_alert("Sorry, we can't find the user");
}, 1000);
</script>
{% endif %}
{% endblock %}
Loading