diff --git a/app/app/urls.py b/app/app/urls.py index 0a631605d01..a20b888ac9b 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -232,6 +232,11 @@ re_path(r'^youtube/?', retail.views.youtube, name='youtube'), re_path(r'^web3/?', retail.views.web3, name='web3'), + # increase funding limit + re_path(r'^requestincrease/?', + retail.views.increase_funding_limit_request, + name='increase_funding_limit_request'), + # link shortener url(r'^l/(.*)$/?', linkshortener.views.linkredirect, name='redirect'), url(r'^credit/(.*)$/?', credits.views.credits, name='credit'), diff --git a/app/assets/v2/js/pages/increase_funding_limit_request_form.js b/app/assets/v2/js/pages/increase_funding_limit_request_form.js new file mode 100644 index 00000000000..12dd8e32837 --- /dev/null +++ b/app/assets/v2/js/pages/increase_funding_limit_request_form.js @@ -0,0 +1,64 @@ + +// Overwrite from shared.js +// eslint-disable-next-line no-empty-function +var trigger_form_hooks = function() { +}; + +$(document).ready(function() { + $('[for=id_comment]').append( + ' (500 ' + gettext('characters left') + ')' + ); + $('[name=comment]').bind('input propertychange', function() { + this.value = this.value.replace(/ +(?= )/g, ''); + + if (this.value.length > 500) { + this.value = this.value.substring(0, 500); + } + + $('#charcount').html(500 - this.value.length); + }); + + const form = $('#increase_request_form'); + + form.validate({ + submitHandler: function(form) { + const inputElements = $(form).find(':input'); + const formData = {}; + + inputElements.removeAttr('disabled'); + $.each($(form).serializeArray(), function() { + formData[this.name] = this.value; + }); + + inputElements.attr('disabled', 'disabled'); + loading_button($('.js-submit')); + + const payload = JSON.stringify(formData); + + $.post('/requestincrease', payload).then( + function(result) { + inputElements.removeAttr('disabled'); + $('#requested_by').attr('disabled', 'disabled'); + unloading_button($('.js-submit')); + + $('#primary_form').hide(); + $('#success_container').show(); + } + ).fail( + function(result) { + inputElements.removeAttr('disabled'); + $('#requested_by').attr('disabled', 'disabled'); + unloading_button($('.js-submit')); + + var alertMsg = result && result.responseJSON ? result.responseJSON.error : null; + + if (alertMsg === null) { + alertMsg = gettext('Network error. Please reload the page and try again.'); + } + + _alert({ message: alertMsg }, 'error'); + } + ); + } + }); +}); diff --git a/app/dashboard/tip_views.py b/app/dashboard/tip_views.py index f87b8ff3921..1ef33d506be 100644 --- a/app/dashboard/tip_views.py +++ b/app/dashboard/tip_views.py @@ -21,6 +21,7 @@ import json import logging +from django.conf import settings from django.contrib import messages from django.http import JsonResponse from django.shortcuts import redirect @@ -329,12 +330,21 @@ def send_tip_3(request): if this_tip.value_in_usdt_now: tips_last_week_value += this_tip.value_in_usdt_now is_over_tip_weekly_limit = tips_last_week_value > request.user.profile.max_tip_amount_usdt_per_week + + increase_funding_form_title = _('Request a Funding Limit Increasement') + increase_funding_form = f'{increase_funding_form_title}' + if is_over_tip_tx_limit: response['status'] = 'error' - response['message'] = _('This tip is over the per-transaction limit of $') + str(max_per_tip) + ('. Please try again later or contact support.') + response['message'] = _('This tip is over the per-transaction limit of $') +\ + str(max_per_tip) + '. ' + increase_funding_form elif is_over_tip_weekly_limit: response['status'] = 'error' - response['message'] = _('You are over the weekly tip send limit of $') + str(request.user.profile.max_tip_amount_usdt_per_week) + ('. Please try again later or contact support.') + response['message'] = _('You are over the weekly tip send limit of $') +\ + str(request.user.profile.max_tip_amount_usdt_per_week) +\ + '. ' + increase_funding_form + return JsonResponse(response) diff --git a/app/marketing/mails.py b/app/marketing/mails.py index 14fa94bf24f..6916d3e0507 100644 --- a/app/marketing/mails.py +++ b/app/marketing/mails.py @@ -759,3 +759,31 @@ def new_bounty_request(model): ) finally: translation.activate(cur_language) + + +def new_funding_limit_increase_request(profile, cleaned_data): + to_email = 'founders@gitcoin.co' + from_email = profile.email or settings.SERVER_EMAIL + cur_language = translation.get_language() + usdt_per_tx = cleaned_data.get('usdt_per_tx', 0) + usdt_per_week = cleaned_data.get('usdt_per_week', 0) + comment = cleaned_data.get('comment', '') + accept_link = f'{settings.BASE_URL}requestincrease?'\ + f'profile_pk={profile.pk}&'\ + f'usdt_per_tx={usdt_per_tx}&'\ + f'usdt_per_week={usdt_per_week}' + + try: + setup_lang(to_email) + subject = _('New Funding Limit Increase Request') + body = f'New Funding Limit Request from {profile} ({profile.absolute_url}).\n\n'\ + f'New Limit in USD per Transaction: {usdt_per_tx}\n'\ + f'New Limit in USD per Week: {usdt_per_week}\n\n'\ + f'To accept the Funding Limit, visit: {accept_link}\n'\ + f'Administration Link: ({settings.BASE_URL}_administrationdashboard/profile/'\ + f'{profile.pk}/change/#id_max_tip_amount_usdt_per_tx)\n\n'\ + f'Comment:\n{comment}' + + send_mail(from_email, to_email, subject, body, from_name=_("No Reply from Gitcoin.co")) + finally: + translation.activate(cur_language) diff --git a/app/retail/forms.py b/app/retail/forms.py new file mode 100644 index 00000000000..50356780463 --- /dev/null +++ b/app/retail/forms.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +"""Define retail related forms. + +Copyright (C) 2018 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 . + +""" +from django import forms +from django.utils.html import escape, strip_tags +from django.utils.translation import gettext_lazy as _ + + +class FundingLimitIncreaseRequestForm(forms.Form): + """Define the FundingLimitIncreaseRequestForm handling.""" + + usdt_per_tx = forms.DecimalField(label=_('New Limit in USD per Transaction'), initial=500, max_digits=50) + usdt_per_week = forms.DecimalField(label=_('New Limit in USD per Week'), initial=1500, max_digits=50) + comment = forms.CharField(max_length=500, widget=forms.Textarea) + + def __init__(self, *args, **kwargs): + super(FundingLimitIncreaseRequestForm, self).__init__(*args, **kwargs) + + self.fields['comment'].widget.attrs['placeholder'] = _('Please tell us why you need a limit increase.') + self.fields['comment'].widget.attrs['rows'] = '4' + self.fields['comment'].widget.attrs['cols'] = '50' + + for field in self.fields: + self.fields[field].widget.attrs['class'] = 'form__input' + + def clean_comment(self): + comment = self.cleaned_data['comment'] or '' + return escape(strip_tags(comment)) diff --git a/app/retail/templates/increase_funding_limit_request_form.html b/app/retail/templates/increase_funding_limit_request_form.html new file mode 100644 index 00000000000..8f04dc4d0ed --- /dev/null +++ b/app/retail/templates/increase_funding_limit_request_form.html @@ -0,0 +1,81 @@ +{% comment %} + Copyright (C) 2018 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 . +{% endcomment %} +{% load i18n static %} + + + + {% include 'shared/head.html' %} + {% include 'shared/cards.html' %} + + +
+ {% include 'shared/nav.html' %} +
+
+
+
+
+
+
+

{{ title }}

+

{{ card_desc }}

+
+ + +
+
+ {% for field in form %} +
+ + {{ field }} +
+ {% endfor %} + +
+
+
+ +
+
+ {% include 'shared/newsletter.html' %} +
+
+
+
+ {% include 'shared/bottom_notification.html' %} + {% include 'shared/analytics.html' %} + {% include 'shared/footer_scripts.html' %} + {% include 'shared/footer.html' %} + {% include 'shared/messages.html' %} + + + + diff --git a/app/retail/views.py b/app/retail/views.py index 6b3b6f3655c..ddb6974e5ee 100644 --- a/app/retail/views.py +++ b/app/retail/views.py @@ -16,6 +16,7 @@ along with this program. If not, see . ''' +from json import loads as json_parse from os import walk as walkdir from django.conf import settings @@ -36,11 +37,13 @@ from dashboard.models import Activity, Profile from dashboard.notifications import amount_usdt_open_work, open_bounties from economy.models import Token -from marketing.mails import new_token_request +from marketing.mails import new_funding_limit_increase_request, new_token_request from marketing.models import Alumni, LeaderboardRank from marketing.utils import get_or_save_email_subscriber, invite_to_slack +from ratelimit.decorators import ratelimit from retail.helpers import get_ip +from .forms import FundingLimitIncreaseRequestForm from .utils import build_stat_results, programming_languages @@ -1068,3 +1071,52 @@ def ui(request): def lbcheck(request): return HttpResponse(status=200) + + +@csrf_exempt +@ratelimit(key='ip', rate='5/m', method=ratelimit.UNSAFE, block=True) +def increase_funding_limit_request(request): + user = request.user if request.user.is_authenticated else None + profile = request.user.profile if user and hasattr(request.user, 'profile') else None + usdt_per_tx = request.GET.get('usdt_per_tx', None) + usdt_per_week = request.GET.get('usdt_per_week', None) + is_staff = user.is_staff if user else False + + if is_staff and usdt_per_tx and usdt_per_week: + try: + profile_pk = request.GET.get('profile_pk', None) + target_profile = Profile.objects.get(pk=profile_pk) + target_profile.max_tip_amount_usdt_per_tx = usdt_per_tx + target_profile.max_tip_amount_usdt_per_week = usdt_per_week + target_profile.save() + except Exception as e: + return JsonResponse({'error': str(e)}, status=400) + + return JsonResponse({'msg': _('Success')}, status=200) + + if request.body: + if not user or not profile or not profile.handle: + return JsonResponse( + {'error': _('You must be Authenticated via Github to use this feature!')}, + status=401) + + try: + result = FundingLimitIncreaseRequestForm(json_parse(request.body)) + if not result.is_valid(): + raise + except Exception as e: + return JsonResponse({'error': _('Invalid JSON.')}, status=400) + + new_funding_limit_increase_request(profile, result.cleaned_data) + + return JsonResponse({'msg': _('Request received.')}, status=200) + + form = FundingLimitIncreaseRequestForm() + params = { + 'form': form, + 'title': _('Request a Funding Limit Increase'), + 'card_title': _('Gitcoin - Request a Funding Limit Increase'), + 'card_desc': _('Do you hit the Funding Limit? Request a increasement!') + } + + return TemplateResponse(request, 'increase_funding_limit_request_form.html', params)