supporters: Fix monthly amounts, validate minimum amount in frontend, update PayPal info

This commit is contained in:
Ben Sturmfels 2024-10-23 18:10:43 +11:00
parent e9f0909d8c
commit 68c5199bb5
Signed by: bsturmfels
GPG key ID: 023C05E2C9C068F0
5 changed files with 34 additions and 17 deletions

View file

@ -3,9 +3,6 @@ from django.utils.safestring import mark_safe
from django.urls import reverse from django.urls import reverse
from .models import SustainerOrder from .models import SustainerOrder
MONTH_MINIMUM = 10
YEAR_MINIMUM = 120
class SustainerFormRenderer(forms.renderers.DjangoTemplates): class SustainerFormRenderer(forms.renderers.DjangoTemplates):
# Customised layout with labels on own row # Customised layout with labels on own row
field_template_name = 'supporters/field.html' field_template_name = 'supporters/field.html'
@ -33,8 +30,10 @@ class SustainerForm(forms.ModelForm):
template_name = 'supporters/sustainer_form.html' template_name = 'supporters/sustainer_form.html'
MONTH_OPTIONS = [12, 25, 50, 100] MONTH_OPTIONS = [12, 23, 45, 87]
YEAR_OPTIONS = [128, 250, 500, 1000] YEAR_OPTIONS = [128, 256, 512, 1024]
MONTH_MINIMUM = 10
YEAR_MINIMUM = 120
class Meta: class Meta:
model = SustainerOrder model = SustainerOrder
@ -70,6 +69,8 @@ class SustainerForm(forms.ModelForm):
# So we can write to this field easily from Alpine JS. # So we can write to this field easily from Alpine JS.
self.fields['amount'].widget.attrs['x-ref'] = 'amount' self.fields['amount'].widget.attrs['x-ref'] = 'amount'
self.fields['amount'].widget.attrs['style'] = 'width: 5rem' self.fields['amount'].widget.attrs['style'] = 'width: 5rem'
self.fields['amount'].widget.attrs['onblur'] = 'this.reportValidity()'
self.fields['amount'].widget.attrs['x-bind:min'] = 'amount_minimum'
self.fields['email'].help_text = 'For your payment receipt' self.fields['email'].help_text = 'For your payment receipt'
self.fields['tshirt_size'].help_text = mark_safe("""Sizing chart: <a href="/videos/women-2017-to-2020-t-shirt-sizing.jpg" target="_blank" class="black-60">Women's</a>, <a href="/videos/men-2017-to-2020-t-shirt-sizing.jpg" target="_blank" class="black-60">Men's</a>""") self.fields['tshirt_size'].help_text = mark_safe("""Sizing chart: <a href="/videos/women-2017-to-2020-t-shirt-sizing.jpg" target="_blank" class="black-60">Women's</a>, <a href="/videos/men-2017-to-2020-t-shirt-sizing.jpg" target="_blank" class="black-60">Men's</a>""")
self.fields['tshirt_size'].widget.attrs['x-model'] = 'tshirt_size' self.fields['tshirt_size'].widget.attrs['x-model'] = 'tshirt_size'
@ -78,7 +79,7 @@ class SustainerForm(forms.ModelForm):
super().clean() super().clean()
recurring = self.cleaned_data.get('recurring', '') recurring = self.cleaned_data.get('recurring', '')
amount = self.cleaned_data.get('amount', 0) amount = self.cleaned_data.get('amount', 0)
minimum = MONTH_MINIMUM if recurring == 'month' else YEAR_MINIMUM minimum = self.MONTH_MINIMUM if recurring == 'month' else self.YEAR_MINIMUM
donate_url = reverse('donate') donate_url = reverse('donate')
if amount < minimum: if amount < minimum:
self.add_error( self.add_error(

View file

@ -7,6 +7,7 @@
.button-select label > span { .button-select label > span {
text-align: center; text-align: center;
display: inline-block; display: inline-block;
user-select: none;
padding: 0.5rem 0; padding: 0.5rem 0;
width: 100%; width: 100%;
background: #ddd; background: #ddd;

View file

@ -1,2 +1,2 @@
<!-- Custom <span> wrapper around the label to enable radio fields to be styled like buttons. --> <!-- Custom <span> wrapper around the label to enable radio fields to be styled like buttons. -->
{% if widget.wrap_label %}<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} <span>{{ widget.label }}</span></label>{% endif %} {% if widget.wrap_label %}<label onclick="click()"{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} <span>{{ widget.label }}</span></label>{% endif %}

View file

@ -4,5 +4,12 @@
{% block category %}sustainer{% endblock %} {% block category %}sustainer{% endblock %}
{% block content %} {% block content %}
<h1 class="lh-title tc mt4 mb4">Thanks!</h1> <h1 class="lh-title tc mt4">Thanks</h1>
<p class="measure-wide center tc">Thank you for helping make our work possible!</p>
<div class="mt4 tc">
<img src="{% static 'img/conservancy-supporter-heart.png' %}" alt="Heart with Conservancy logo">
</div>
<p class="tc mt4 mb5">Return to our <a href="{% url 'sustainers' %}">sustainers</a> page.</p>
{% endblock %} {% endblock %}

View file

@ -63,25 +63,33 @@
let year_options = {{ form.YEAR_OPTIONS|escapejs }}; let year_options = {{ form.YEAR_OPTIONS|escapejs }};
return this.recurring === 'month' ? month_options : year_options; return this.recurring === 'month' ? month_options : year_options;
}, },
amount_minimum: function() {
let month_minimum = {{ form.MONTH_MINIMUM|escapejs }};
let year_minimum = {{ form.YEAR_MINIMUM|escapejs }};
return this.recurring === 'month' ? month_minimum : year_minimum;
},
tshirt_size: '{{ form.tshirt_size.value|escapejs }}', tshirt_size: '{{ form.tshirt_size.value|escapejs }}',
}"> }">
{% csrf_token %} {% csrf_token %}
<fieldset class="bg-black-05 pa3 br3 center" style="border: 1px solid #ccc"> <fieldset class="bg-black-05 pa3 br3 center" style="border: 1px solid #ccc">
<legend class="b f5">Become a Sustainer</legend> <legend class="b f5">Become a Sustainer</legend>
{{ form.non_field_errors }} {{ form.non_field_errors }}
<div>{{ form.recurring.as_field_group }}</div> <div>{{ form.recurring.as_field_group }}</div>
<div class="mt3"> <div class="mt3">
<div id="amount_options" class="button-select"> <div id="amount_options" class="button-select">
<template x-for="m in amount_options"> <template x-for="m in amount_options">
<label> {# Additional click handler ensures a click-drag activates the radio (similar to a real button). #}
<input type="radio" name="amount_option" x-bind:value="m" x-on:change="$refs.amount.value = $event.currentTarget.value" x-model="amount_option" required> <label onclick="this.click()">
{# It seems to be important that all radios have a unique value to avoid UI glitches. #}
<input type="radio" name="amount_option" x-bind:value="m" x-on:change="$refs.amount.value = m" x-model="amount_option" required>
<span>$<span x-text="m.toLocaleString()"></span></span> <span>$<span x-text="m.toLocaleString()"></span></span>
</label> </label>
</template> </template>
<!-- Hide if no JS --> <!-- Hide if no JS -->
<template x-if="true"> <template x-if="true">
<label> <label onclick="this.click()">
<input type="radio" name="amount_option" value="other" x-on:change="$refs.amount.value = ''" x-model="amount_option" required> <input type="radio" name="amount_option" value="other" x-on:change="$refs.amount.value = ''" x-model="amount_option" required>
<span>Other</span> <span>Other</span>
</label> </label>
@ -122,7 +130,12 @@
</fieldset> </fieldset>
</form> </form>
<p class="f7 mt3">Credit card and ACH payments are processed with Stripe. We also accept payment by paper check and wire transfer and PayPal (see below). Our sustainer program has a minimum of $120 USD per year, but we also accept <a href="/donate/">donations of smaller amounts</a>.</p> <p class="f7 mt3">Credit card and ACH payments are processed with Stripe. We also accept payment by PayPal, paper check and wire transfer (see below). Our sustainer program has a minimum of $120 USD per year, but we also accept <a href="/donate/">donations of smaller amounts</a>.</p>
<details id="paypal">
<summary class="f6">PayPal</summary>
<p>If you would prefer not to use our Stripe payment service above you can use <a href="{% url 'sustainer_paypal' %}">PayPal</a>.</p>
</details>
<details id="wire-transfer"> <details id="wire-transfer">
<summary class="f6">Wire Transfer</summary> <summary class="f6">Wire Transfer</summary>
@ -139,11 +152,6 @@
<p>Please write <q>SUSTAINER</q>, T-shirt size, if you are renewing, and if <p>Please write <q>SUSTAINER</q>, T-shirt size, if you are renewing, and if
you want public acknowledgment in memo line.</p> you want public acknowledgment in memo line.</p>
</details> </details>
<details id="paypal">
<summary class="f6">PayPal</summary>
<p>Please visit our <a href="{% url 'sustainer_paypal' %}">Become a Sustainer by PayPal</a> page.</p>
</details>
</section> </section>
<section style="grid-row: 1 / span 2"> <section style="grid-row: 1 / span 2">