Make Kassa communicate with Dusken.

This commit is contained in:
ponas committed 2017-07-27 15:52:14 +02:00
1 parent f400d7a295
commit 031a400855
21 files changed
+14731 -7321

No files matched your search

+16 -1
View File
@@ -12,7 +12,22 @@ Kassa helps you register purchased membership cards and memberships. It's a tool
pip install -r requirements.txt
python manage.py migrate
### LDAP
## Configuration
Create a user in Dusken with the following permissions:
* dusken | User | Can view User
* dusken | Member card | Can change Member card
* dusken | Member card | Can view Member card
* dusken | Membership | Can add Membership
* dusken | Membership | Can view Membership
* dusken | Order | Can view Order
Fetch DUSKEN_API_KEY:
curl -H "Content-Type: application/json" -X POST -d '{"username":"apiuser","password":"test"}' DUSKEN_API_URL/auth/obtain-token/
If developing you can disable calls to Tekstmelding in local_settings.py:
TEKSTMELDING_ENABLED = False
## LDAP
sudo docker run -e LDAP_DOMAIN=neuf.no -e LDAP_ORGANISATION="Neuf" -e LDAP_ADMIN_PASSWORD="toor" -p 389:389 -d nikolaik/openldap
ldapadd -D "cn=admin,dc=neuf,dc=no" -w "toor" -f test/testdata.ldif # Testdata
# Verify import
+4 -2
View File
@@ -3,8 +3,10 @@ from django.contrib import admin
class KassaEventAdmin(admin.ModelAdmin):
list_display = ['id', 'event', 'card_number', 'user_phone_number', 'user_inside_id', 'created']
list_display = ['id', 'event', 'card_number', 'user_phone_number', 'user_dusken_id',
'user_inside_id', 'created', 'transaction_id']
list_filter = ['event']
search_fields = ['card_number', 'user_phone_number', 'user_inside_id']
search_fields = ['card_number', 'user_phone_number', 'user_dusken_id',
'user_inside_id', 'transaction_id']
admin.site.register(KassaEvent, KassaEventAdmin)
+1
View File
@@ -14,3 +14,4 @@ class AddCardForm(forms.Form):
card_number = forms.IntegerField(widget=forms.NumberInput(attrs={'autofocus': True}))
phone_number = PhoneNumberField(widget=forms.TextInput(attrs={'type': 'tel'}))
user_id = forms.IntegerField(widget=forms.HiddenInput(), required=False)
order_uuid = forms.IntegerField(widget=forms.HiddenInput(), required=False)
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-07-22 12:40
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('kassa', '0006_auto_20150829_1811'),
]
operations = [
migrations.AddField(
model_name='kassaevent',
name='transaction_id',
field=models.UUIDField(blank=True, null=True),
),
]
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-07-22 12:43
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('kassa', '0007_kassaevent_transaction_id'),
]
operations = [
migrations.AddField(
model_name='kassaevent',
name='user_dusken_id',
field=models.IntegerField(blank=True, null=True),
),
]
+2
View File
@@ -21,7 +21,9 @@ class KassaEvent(models.Model):
)
event = models.CharField(max_length=255, choices=EVENT_CHOICES)
card_number = models.IntegerField(null=True, blank=True)
user_dusken_id = models.IntegerField(null=True, blank=True)
user_inside_id = models.IntegerField(null=True, blank=True)
user_phone_number = models.CharField(max_length=100, null=True, blank=True)
transaction_id = models.UUIDField(null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
+1 -1
View File
@@ -18,7 +18,7 @@
{{ add_card_form|bootstrap }}
<div class="register-card-form--selected-user-section">
<label>{% trans "Member" %}</label>
<div class="register-card-form--selected-user-wrap"></div>
<div class="register-card-form--selected-entity-wrap"></div>
</div>
<button id="register-submit-btn" type="submit" class="btn btn-primary btn-lg" disabled>{% trans "Register card" %}</button>
+6 -5
View File
@@ -1,12 +1,13 @@
from django.conf.urls import url
from apps.kassa.views import register, inside_user_api, inside_card_api, check_phone_number, \
register_card_and_membership, stats_card_sales, renew_membership
from apps.kassa.views import (register, user_search, check_card,
check_phone_number, register_card_and_membership,
stats_card_sales, renew_membership)
urlpatterns = [
url(r'^$', register, name='register'),
url(r'^inside/user/$', inside_user_api, name='inside-user-api'),
url(r'^check-phonenumber/$', check_phone_number, name='check-phone-number'),
url(r'^inside/card/$', inside_card_api, name='inside-card-number-api'),
url(r'^user-search/$', user_search, name='user-search'),
url(r'^check-phone-number/$', check_phone_number, name='check-phone-number'),
url(r'^check-card/$', check_card, name='check-card'),
url(r'^register-card-membership/$', register_card_and_membership, name='register-card-and-membership'),
url(r'^renew-membership/$', renew_membership, name='renew-membership'),
url(r'^stats/card-sales/$', stats_card_sales, name='stats-card-sales')
+68 -36
View File
@@ -3,6 +3,13 @@ from django.conf import settings
import datetime
import phonenumbers
import requests
import logging
logger = logging.getLogger(__name__)
dusken_auth = {
'Authorization': 'Token {}'.format(settings.DUSKEN_API_KEY)
}
def tekstmelding_new_membership_card(card_number, phone_number):
@@ -12,6 +19,10 @@ def tekstmelding_new_membership_card(card_number, phone_number):
'action': 'new_card_no_user',
}
if not settings.TEKSTMELDING_ENABLED:
logger.info('Tekstmelding disabled. Payload would be: ', payload)
return
url = '{}kassa/new-membership-card'.format(settings.TEKSTMELDING_API_URL)
response = requests.post(
url,
@@ -23,48 +34,69 @@ def tekstmelding_new_membership_card(card_number, phone_number):
return response.json()
def inside_get_card(card_number):
url = '{}card.php'.format(settings.INSIDE_API_URL)
params = {
'apikey': settings.INSIDE_API_KEY,
'card_number': card_number
}
return requests.get(url, params=params)
def get_card(card_number):
url = '{}cards/{}/'.format(settings.DUSKEN_API_URL, card_number)
return requests.get(url, headers=dusken_auth)
def inside_update_card(card_number, user_id, phone_number, action, membership_trial=None):
url = '{}card.php'.format(settings.INSIDE_API_URL)
def get_order(order_uuid):
url = '{}orders/{}/'.format(settings.DUSKEN_API_URL, order_uuid)
return requests.get(url, headers=dusken_auth)
def get_latest_order_by_card(card_number):
url = '{}orders/'.format(settings.DUSKEN_API_URL)
payload = {'card_number': card_number}
orders = requests.get(url, params=payload, headers=dusken_auth).json()
return orders.get('results')[0]
def get_latest_order_by_phone(phone_number):
url = '{}orders/'.format(settings.DUSKEN_API_URL)
payload = {'phone_number': phone_number}
orders = requests.get(url, params=payload, headers=dusken_auth).json()
if orders.get('count'):
return orders.get('results')[0]
return None
def get_user(user_id):
url = '{}users/{}/'.format(settings.DUSKEN_API_URL, user_id)
return requests.get(url, headers=dusken_auth)
def get_user_by_phone(phone_number):
url = '{}users/'.format(settings.DUSKEN_API_URL)
payload = {'phone_number': phone_number}
users = requests.get(url, params=payload, headers=dusken_auth).json()
if users.get('count') == 1:
return users.get('results')[0]
return None
def update_card(card_number, user_id=None, order_uuid=None):
assert not (user_id and order_uuid)
url = '{}kassa/card/'.format(settings.DUSKEN_API_URL)
payload = {
'card_number': card_number,
'user_id': user_id,
'member_card': card_number,
'user': user_id,
'order': order_uuid,
}
return requests.patch(url, json=payload, headers=dusken_auth)
def update_membership(user=None, phone_number=None, card_number=None, membership_type=None,
transaction_id=None):
assert membership_type in ('standard', 'trial')
url = '{}kassa/membership/'.format(settings.DUSKEN_API_URL)
payload = {
'user': user,
'phone_number': phone_number,
'action': action # new_card_membership, update_card, add_or_renew, sms_card_notify
'member_card': card_number,
'membership_type': membership_type,
'transaction_id': transaction_id,
}
if membership_trial is not None:
payload.update({'membership_trial': membership_trial})
return requests.post(
url,
json=payload,
params={'apikey': settings.INSIDE_API_KEY},
headers=dict(content_type='application/json')
)
def inside_update_membership(user_id, purchased=None, membership_trial=None):
url = '{}membership.php'.format(settings.INSIDE_API_URL)
payload = {
'apikey': settings.INSIDE_API_KEY,
'user_id': user_id,
'purchased': purchased,
'source': 'kassa'
}
if membership_trial is not None:
payload.update({'membership_trial': membership_trial})
return requests.post(url, json=payload, headers=dict(content_type='application/json'))
return requests.post(url, json=payload, headers=dusken_auth)
def format_phone_number(number):
+84 -70
View File
@@ -6,14 +6,16 @@ from django.shortcuts import render
from django.utils import timezone
from itertools import groupby
import json
import uuid
import logging
import phonenumbers
import requests
from apps.kassa.models import KassaEvent
from apps.kassa.forms import AddCardForm, SearchUserForm
from apps.kassa.utils import tekstmelding_new_membership_card, inside_update_card, inside_get_card, \
inside_update_membership, format_phone_number, is_autumn
from apps.kassa.utils import tekstmelding_new_membership_card, update_card, get_card, \
get_order, get_latest_order_by_card, get_latest_order_by_phone, get_user, get_user_by_phone, \
dusken_auth, update_membership, format_phone_number, is_autumn
logger = logging.getLogger(__name__)
@@ -29,18 +31,17 @@ def register(request):
@login_required
def inside_user_api(request):
def user_search(request):
payload = {
'apikey': settings.INSIDE_API_KEY,
'q': request.GET.get('q', '')
'search': request.GET.get('search', '')
}
url = '{}user.php'.format(settings.INSIDE_API_URL)
data = requests.get(url, params=payload).json()
url = '{}users/'.format(settings.DUSKEN_API_URL)
data = requests.get(url, params=payload, headers=dusken_auth).json()
return JsonResponse(data)
@login_required()
@login_required
def check_phone_number(request):
number = request.GET.get('phone_number', '').strip()
@@ -56,115 +57,128 @@ def check_phone_number(request):
return JsonResponse({'error': str(e).replace('(1) ', '')})
number = phonenumbers.format_number(p, phonenumbers.PhoneNumberFormat.E164)
# Inside lookup existing user
payload = {
'apikey': settings.INSIDE_API_KEY,
'q': number
}
url = '{}phonenumber.php'.format(settings.INSIDE_API_URL)
inside_data = requests.get(url, params=payload).json()
# Tekstmelding lookup pending memberships
payload = {
'api_key': settings.TEKSTMELDING_API_KEY,
'number': number
}
url = '{}kassa/pending-membership'.format(settings.TEKSTMELDING_API_URL)
tekstmelding_data = requests.get(url, params=payload).json()
user = get_user_by_phone(number)
order = get_latest_order_by_phone(number)
return JsonResponse({
'tekstmelding': tekstmelding_data,
'inside': inside_data
'user': user,
'order': order
})
@login_required
def inside_card_api(request):
def check_card(request):
if request.method != 'GET':
return JsonResponse({'error': 'Only method GET supported'})
response = inside_get_card(request.GET.get('card_number'))
return JsonResponse(response.json(), status=response.status_code)
card_number = request.GET.get('card_number')
response = get_card(card_number)
if response.status_code != 200:
return JsonResponse(response.json(), status=response.status_code)
card = response.json()
if card.get('user'):
card['user'] = get_user(card['user']).json()
card['order'] = None
if card.get('orders'):
card['order'] = get_latest_order_by_card(card_number)
card.pop('orders')
return JsonResponse(card, status=response.status_code)
@login_required()
@login_required
def register_card_and_membership(request):
if request.method != 'POST':
return JsonResponse({'error': 'Only method POST supported'})
post_data = json.loads(request.body.decode('utf-8'))
action = post_data.get('action') # new_card_membership, update_card, add_or_renew, sms_card_notify
# new_card_membership, update_card, add_or_renew, sms_card_notify
# TODO: multiple actions (to allow renewal only)
action = post_data.get('action')
user_id = post_data.get('user_id')
order_uuid = post_data.get('order_uuid')
card_number = post_data.get('card_number')
phone_number = post_data.get('phone_number')
membership_trial = post_data.get('membership_trial')
membership_type = 'trial' if membership_trial else 'standard'
response = inside_update_card(
card_number,
user_id,
post_data.get('phone_number'),
action,
membership_trial
)
if response.status_code != 200:
return JsonResponse(response.json(), status=response.status_code)
# Update card number on user or order
if action in ('update_card', 'sms_card_notify'):
if action == 'update_card' and user_id:
response = update_card(card_number, user_id=user_id)
elif action == 'sms_card_notify' and order_uuid:
response = update_card(card_number, order_uuid=order_uuid)
else:
assert user_id or order_uuid
if response.status_code != 200:
return JsonResponse(response.json(), status=response.status_code)
event_data = {
'event': KassaEvent.UPDATE_CARD,
'user_phone_number': format_phone_number(post_data.get('phone_number')),
'card_number': card_number
}
if user_id:
event_data.update({'user_inside_id': user_id})
event_data = {
'event': KassaEvent.UPDATE_CARD,
'user_phone_number': phone_number,
'card_number': card_number,
'user_dusken_id': user_id,
}
logger.debug(event_data)
KassaEvent.objects.create(**event_data)
logger.debug(event_data)
KassaEvent.objects.create(**event_data)
card_update_response = response.json()
# Add initial or renew membership for existing user or order
if action in ('add_or_renew', 'new_card_membership'):
transaction_id = uuid.uuid4()
response = update_membership(
user=user_id,
phone_number=phone_number,
card_number=card_number,
membership_type=membership_type,
transaction_id=str(transaction_id))
KassaEvent.objects.create(
event=KassaEvent.ADD_OR_RENEW if membership_type != 'trial' else KassaEvent.MEMBERSHIP_TRIAL,
user_dusken_id=user_id,
card_number=card_number,
user_phone_number=phone_number,
transaction_id=transaction_id
)
# Send activation notification (link) to user by SMS
if action == 'new_card_membership' or action == 'sms_card_notify':
card = card_update_response['card']
if action in ('new_card_membership', 'sms_card_notify'):
event = KassaEvent.NEW_CARD_MEMBERSHIP
if action == 'sms_card_notify':
event = KassaEvent.SMS_CARD_NOTIFY
elif event == KassaEvent.NEW_CARD_MEMBERSHIP and membership_trial is not None:
event = KassaEvent.MEMBERSHIP_TRIAL
# FIXME: could be async
tekstmelding_new_membership_card(card_number=card['card_number'], phone_number=card['owner_phone_number'])
tekstmelding_new_membership_card(card_number=card_number,
phone_number=phone_number)
KassaEvent.objects.create(
event=event,
card_number=card['card_number'],
user_phone_number=card['owner_phone_number']
)
# Add initial or renew membership for existing user
elif action == 'add_or_renew':
response = inside_update_membership(user_id, post_data.get('purchased'), membership_trial=membership_trial)
KassaEvent.objects.create(
event=KassaEvent.ADD_OR_RENEW if membership_trial is None else KassaEvent.MEMBERSHIP_TRIAL,
user_inside_id=user_id
card_number=card_number,
user_phone_number=phone_number
)
return JsonResponse(response.json(), status=response.status_code)
@login_required()
@login_required
def renew_membership(request):
if request.method != 'POST':
return JsonResponse({'error': 'Only method POST supported'})
post_data = json.loads(request.body.decode('utf-8'))
user_id = post_data.get('user_id')
phone_number = post_data.get('phone_number')
card_number = post_data.get('card_number')
membership_trial = post_data.get('membership_trial')
membership_type = 'trial' if membership_trial else 'standard'
transaction_id = str(uuid.uuid4())
response = inside_update_membership(user_id, purchased=post_data.get('purchased'), membership_trial=membership_trial)
response = update_membership(
user=user_id,
phone_number=phone_number,
card_number=card_number,
membership_type=membership_type,
transaction_id=transaction_id)
KassaEvent.objects.create(
event=KassaEvent.RENEW_ONLY if membership_trial is None else KassaEvent.MEMBERSHIP_TRIAL,
user_inside_id=user_id
user_dusken_id=user_id,
transaction_id=transaction_id
)
return JsonResponse(response.json(), status=response.status_code)
+3 -2
View File
@@ -113,11 +113,12 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
LOGIN_REDIRECT_URL = '/'
INSIDE_API_KEY = ''
INSIDE_API_URL = 'https://inside.studentersamfundet.no/api/'
DUSKEN_API_KEY = ''
DUSKEN_API_URL = 'https://dusken.neuf.no/api/'
TEKSTMELDING_API_KEY = ''
TEKSTMELDING_API_URL = 'https://tekstmelding.neuf.no/'
TEKSTMELDING_ENABLED = True
CORS_ORIGIN_ALLOW_ALL = True
+185 -123
View File
@@ -20,20 +20,7 @@ function getParameterByName(name) {
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
function update_user_with_card_details(user) {
/* Cards with a number lower than this is legacy as of 2015-08 */
var legacy_card_limit = 100000000;
for(var j=0; j<user.cards.length; j++) {
if(parseInt(user.cards[j].card_number, 10) < legacy_card_limit) {
user.cards[j].is_legacy = "1";
}
if(user.cards[j].is_active == "1") {
user.card_number_active = user.cards[j].card_number;
}
}
return user;
}
function format_phone_number(str) {
if(!str || str.length <= 1) {
@@ -46,20 +33,36 @@ function format_phone_number(str) {
return str;
}
function sort_search_results(results) {
return _.sortBy(results, function (x) {
if (x.last_membership) {
if (x.last_membership.end_date) {
return 0 - moment(x.last_membership.end_date).unix();
}
return 0 - moment(x.last_membership.start_date).unix();
}
return 0;
});
}
function format_results(results) {
results = _.map(results, update_user_with_card_details);
return nunjucksEnv.render('search_results.html', {'results': results});
}
function render_selected_user(user_data) {
var context;
if(user_data === undefined || user_data === null) {
if (!user_data) {
context = {placeholder: true}; // dummy card
} else {
user_data = update_user_with_card_details(user_data);
context = {res: user_data, checked: true, no_user_actions: true};
}
var selected_user_html = nunjucksEnv.render('search_result.html', context);
_dom.selectedUserWrap.html(selected_user_html);
_dom.selectedEntityWrap.html(selected_user_html);
}
function render_selected_order(order) {
if (!order) { return; }
var context = {order: order, checked: true, no_user_actions: true};
var selected_order_html = nunjucksEnv.render('selected_order.html', context);
_dom.selectedEntityWrap.html(selected_order_html);
}
function set_toast(message, message_type) {
var context = {
@@ -76,15 +79,9 @@ function set_toast(message, message_type) {
}
function set_selected_user(user, update_search_result) {
selectedUser = user;
var new_val = '';
updateMemberShipButton(user, null);
if(user) {
new_val = user.id;
}
updateMemberShipButton(user);
_dom.userIdField.val(new_val).trigger('change');
_dom.userIdField.val((user && user.id) || '').trigger('change');
if(update_search_result) {
var stale_search_result = _dom.results.find('.search-result').removeClass('selected');
stale_search_result.find('input').prop('checked', false);
@@ -95,6 +92,13 @@ function set_selected_user(user, update_search_result) {
}
}
}
function set_selected_order(order) {
membershipOrder = order;
if (order) {
updateMemberShipButton(null, order);
}
_dom.orderUuidField.val((order && order.uuid) || '').trigger('change');
}
function set_field_state(field_element, state, help_text) {
if(!help_text) {
@@ -140,12 +144,10 @@ function resetCardForm(reset_native) {
set_field_state(_dom.cardNumberField, '');
cardForm.fields.cardNumber = false;
selectedUser = null;
membershipOrder = null;
_dom.userIdField.val('').trigger('change');
_dom.membershipTrialCheckBox.prop('checked', false);
pendingSMSMembership = null;
/* Disable submit buttons */
_dom.registerSubmitButton.prop('disabled', true);
_dom.membershipSubmitButton.prop('disabled', true);
@@ -170,16 +172,34 @@ function update_submit_button() {
}
_dom.registerSubmitButton.prop('disabled', true);
}
function updateMemberShipButton(user) {
var today_plus_one_month = moment().add(1, 'month').format('YYYY-MM-DD');
/* Existing member with upcoming expiry and card */
if(user && user.is_member === '1' && user.expires <= today_plus_one_month && user.card_number_active !== '') {
function updateMemberShipButton(user, order) {
var today_plus_thirty_days = moment().add(30, 'days').format('YYYY-MM-DD');
/* User with valid membership */
if (user && user.is_member) {
/* ...which is lifelong cannot renew */
if (user.last_membership.membership_type == 'lifelong') {
_dom.membershipSubmitButton.prop('disabled', true);
}
/* ...or expiring soon and the user has a card */
//else if (user.last_membership.end_date <= today_plus_thirty_days && user.active_member_card) {
/* ...with a membership that expires soon can renew */
else if (user.last_membership.end_date <= today_plus_thirty_days) {
_dom.membershipSubmitButton.prop('enabled', false);
}
}
/* User with expired membership can renew */
// else if (user && !user.is_member && user.active_member_card) {
else if (user && !user.is_member) {
_dom.membershipSubmitButton.prop('disabled', false);
}
/* User with card, but expired membership */
else if(user && user.is_member !== '1' && user.card_number_active !== '') {
_dom.membershipSubmitButton.prop('disabled', false);
} else {
/* Order */
else if (order) {
/* If the membership has expired it can be renewed */
if (!order.product.is_valid && order.product.end_date <= today_plus_thirty_days) {
_dom.membershipSubmitButton.prop('disabled', false);
}
}
else {
_dom.membershipSubmitButton.prop('disabled', true);
}
}
@@ -187,7 +207,7 @@ function validatePhoneNumber(val) {
if(val.length === 0) {
cardForm.fields.phoneNumber = false;
update_submit_button();
return 'Phonenumber number should not be empty.';
return 'Phone number should not be empty.';
}
cardForm.fields.phoneNumber = true;
update_submit_button();
@@ -204,11 +224,11 @@ function validateCardNumber(val) {
cardForm.fields.cardNumber = false;
update_submit_button();
return 'Card number should start with 1.';
}
}
if(val.length !== 9) {
cardForm.fields.cardNumber = false;
update_submit_button();
return 'Card number should be 9 digits long';
return 'Card number should be 9 digits long.';
}
cardForm.fields.cardNumber = true;
update_submit_button();
@@ -218,8 +238,10 @@ function validateCardNumber(val) {
function getFormData(formElement) {
var formData = formElement.serializeArray();
formData = _.object(_.map(formData, function (x) {
return [x.name, x.value];
return [x.name, x.value || null];
}));
/* no need for radio button state */
delete formData.user;
return formData;
}
@@ -231,44 +253,50 @@ function checkPhoneNumber() {
/* Invalid phone number?*/
if(data.error) {
set_selected_user(null, true);
set_selected_order(null);
set_field_state(_dom.phoneNumberField, 'error', data.error);
cardForm.fields.phoneNumber = false;
pendingSMSMembership = null;
update_submit_button();
return;
}
/* Already has valid card membership? */
if(data.inside.card && data.inside.card.has_valid_membership == "1") {
var card = data.inside.card;
var msg = 'Phone number is already tied to card '+ card.card_number + ' and is valid until '+ card.expires +'.';
set_field_state(_dom.phoneNumberField, 'error', msg);
var user = data.user;
var order = data.order;
var message = '';
/* Has valid card membership */
if (!user && order && order.member_card && order.product.is_valid) {
message = 'Phone number is already tied to card '+ order.member_card + ' and is valid until '+ order.product.end_date +'.';
set_field_state(_dom.phoneNumberField, 'error', message);
cardForm.fields.phoneNumber = false;
pendingSMSMembership = null;
set_selected_order(order);
update_submit_button();
return;
}
var inside = data.inside;
var tekstmelding = data.tekstmelding;
var _user = null;
var _success_msg = '';
if(inside.users.length == 1) {
if (user) {
/* Existing user */
_user = inside.users[0];
_success_msg = 'Phone number belongs to existing user.';
pendingSMSMembership = null;
} else if(tekstmelding.result !== null) {
/* Pending SMS membership (not activated yet) */
_success_msg = 'Phone number has valid membership (paid via SMS). OK to give out card.';
pendingSMSMembership = tekstmelding.result;
message = 'Phone number belongs to existing user.';
set_field_state(_dom.phoneNumberField, 'success', message);
set_selected_user(user, true);
set_selected_order(null);
cardForm.fields.phoneNumber = true;
update_submit_button();
} else if (order) {
set_selected_order(order);
/* Valid membership, no card associated */
if (order.product.is_valid) {
message = 'Phone number has a valid membership.';
} else {
message = 'Phone number has an expired membership.';
}
} else {
/* Valid phone number with no existing user, card membership or pending SMS membership */
pendingSMSMembership = null;
/* Valid phone number associated user or order */
set_selected_order(null);
set_selected_user(null);
}
set_field_state(_dom.phoneNumberField, 'success', _success_msg);
set_selected_user(_user, true);
set_field_state(_dom.phoneNumberField, 'success', message);
cardForm.fields.phoneNumber = true;
update_submit_button();
});
@@ -277,8 +305,9 @@ function checkPhoneNumber() {
/* Init and global vars */
var _dom;
var users;
var searching; /* search ajax request */
var selectedUser;
var pendingSMSMembership;
var membershipOrder; /* selected order object */
var cardForm = {
fields: {
cardNumber: false,
@@ -295,9 +324,9 @@ var nunjucksEnv = new nunjucks.Environment();
nunjucksEnv.addFilter('phoneNumber', format_phone_number);
var urls = {
insideUserApi: '/inside/user/',
checkPhoneNumber: '/check-phonenumber/',
insideCard: '/inside/card/',
userSearch: '/user-search/',
checkPhoneNumber: '/check-phone-number/',
checkCard: '/check-card/',
registerCardAndMembership: '/register-card-membership/',
renewMembership: '/renew-membership/'
};
@@ -315,7 +344,8 @@ $(document).ready(function(){
registerSubmitButton: $('#register-submit-btn'),
usernameField: $('#id_username'),
userIdField: $('#id_user_id'),
selectedUserWrap: $('.register-card-form--selected-user-wrap'),
orderUuidField: $('#id_order_uuid'),
selectedEntityWrap: $('.register-card-form--selected-entity-wrap'),
toastWrap: $('.toast-wrap'),
registerResetButton: $('.register-reset-btn'),
searchResetButton: $('.search-reset-btn'),
@@ -339,39 +369,48 @@ $(document).ready(function(){
/* Card number should exist in the database and not tied to existing user */
var val = _dom.cardNumberField.val().trim();
var validation_msg = validateCardNumber(val);
if( validation_msg !== '' ) {
if (validation_msg !== '') {
set_field_state(_dom.cardNumberField, 'error', validation_msg);
return;
}
$.getJSON(urls.insideCard, {card_number: val}, function(data) {
if(data.error) {
$.getJSON(urls.checkCard, {card_number: val}, function(data) {
if (data.error) {
update_submit_button();
cardForm.fields.cardNumber = false;
set_field_state(_dom.cardNumberField, 'error', data.error);
return;
}
var card = data.card;
var user = data.user;
var today_plus_one_month = moment().add(1, 'month');
if(!card) {
var card = data;
var user = data.user;
var order = data.order;
var today_plus_thirty_days = moment().add(30, 'days');
if (!card) {
cardForm.fields.cardNumber = false;
set_field_state(_dom.cardNumberField, 'error', 'Cannot find card number in database.');
set_field_state(_dom.cardNumberField, 'error',
'Cannot find card number in database.');
}
else if(card && card.registered !== "" && user === null && moment(card.expires) <= today_plus_one_month) {
/* An allready registered card (with no user) can repurchase membership if expired */
else if (card && card.registered !== null && user === null && order && !order.product.is_valid) {
/* An already registered card (with no user) can repurchase membership if expired */
set_selected_order(order);
cardForm.fields.cardNumber = true;
set_field_state(_dom.cardNumberField, 'success', 'Card number is in use (not activated) and belongs to ' + format_phone_number(card.owner_phone_number) + '.');
set_field_state(_dom.cardNumberField, 'success',
'Card number is in use (not activated) and belongs to ' + format_phone_number(order.phone_number) + '.');
}
else if(card && card.registered !== "") {
var owner_string = 'phone number: '+ format_phone_number(card.owner_phone_number);
if(user) {
owner_string = 'existing user: ' + user.firstname + ' ' + user.lastname + ' (' + format_phone_number(user.number) + ')';
else if (card && card.registered !== null) {
var owner_string = '???';
if (user) {
owner_string = 'existing user: ' + user.first_name + ' ' + user.last_name + ' (' + format_phone_number(user.phone_number) + ')';
} else {
set_selected_order(order);
owner_string = 'phone number: '+ format_phone_number(order.phone_number);
}
cardForm.fields.cardNumber = false;
set_field_state(_dom.cardNumberField, 'error', 'Card number is in use and belongs to '+ owner_string +'.');
set_field_state(_dom.cardNumberField, 'error',
'Card number is in use and belongs to '+ owner_string +'.');
}
else if(card && user === null) {
else if (card && user === null) {
cardForm.fields.cardNumber = true;
set_field_state(_dom.cardNumberField, 'success');
} else {
@@ -379,6 +418,10 @@ $(document).ready(function(){
set_field_state(_dom.cardNumberField, '');
}
update_submit_button();
}).fail(function(data) {
cardForm.fields.cardNumber = false;
set_field_state(_dom.cardNumberField, 'error',
'Cannot find card number in database.');
});
});
@@ -389,17 +432,21 @@ $(document).ready(function(){
_dom.results.html('');
return;
}
$.getJSON(urls.insideUserApi, {q: val}, function(data) {
if(data.results && data.results.length > 0) {
_dom.results.html(format_results(data.results));
if (searching) {
searching.abort();
}
searching = $.getJSON(urls.userSearch, {search: val}, function(data) {
if (data.results && data.results.length > 0) {
sorted_results = sort_search_results(data.results);
_dom.results.html(format_results(sorted_results));
users = data.results;
}
else if(data.error) {
else if (data.error) {
_dom.results.html(data.error);
}
else {
if(val !== "") {
_dom.results.html("Found no existing user with search param: '"+ val +"'");
_dom.results.html("Found no user matching: '"+ val +"'");
} else {
_dom.results.html('');
}
@@ -420,7 +467,7 @@ $(document).ready(function(){
set_selected_user(_.findWhere(users, {id: user_id}));
/* Update phone number field */
if(number === '-') {
if (number === '-') {
number = '';
}
_dom.phoneNumberField.val(number).trigger('input');
@@ -438,13 +485,13 @@ $(document).ready(function(){
/* Register type */
// FIXME: get from form
// action: 'new_card_membership', 'update_card', 'add_or_renew', 'sms_card_notify'
if(selectedUser === null && pendingSMSMembership === null) {
if (selectedUser === null && membershipOrder === null) {
payload.action = 'new_card_membership';
} else if(selectedUser === null && pendingSMSMembership !== null) {
} else if (selectedUser === null && membershipOrder !== null) {
payload.action = 'sms_card_notify';
payload.purchased = pendingSMSMembership.purchase_date;
payload.order_uuid = membershipOrder.uuid;
} else {
if(selectedUser.is_member === '1') {
if (selectedUser.is_member) {
payload.action = 'update_card';
} else {
payload.action = 'add_or_renew'; // Note: could also update card number
@@ -456,20 +503,17 @@ $(document).ready(function(){
dataType: 'json',
type: 'post',
headers: {'X-CSRFToken': getCookie('csrftoken')}
}).success(function(data){
var phone_number;
}).done(function(data) {
var success_message = 'New card registered ' + payload.card_number;
if(payload.action === 'add_or_renew') {
var full_name = data.user[0].firstname + ' ' + data.user[0].lastname;
if (payload.action === 'add_or_renew') {
var full_name = selectedUser.first_name + ' ' + selectedUser.last_name;
success_message = 'New membership and card number '+ payload.card_number + ' registered to ' + full_name + ' ';
}
else if(payload.action ==='new_card_membership') {
phone_number = data.card.owner_phone_number;
success_message = 'New membership and card registered to ' + format_phone_number(phone_number) + '. Activation SMS sent ';
else if (payload.action ==='new_card_membership') {
success_message = 'New membership and card registered to ' + format_phone_number(payload.phone_number) + '. Activation SMS sent ';
}
else if(payload.action === 'sms_card_notify') {
phone_number = data.card.owner_phone_number;
success_message = 'New card registered to ' + format_phone_number(phone_number) + '. Membership already paid by SMS ('+ pendingSMSMembership.purchase_date +'). Activation SMS sent ';
else if (payload.action === 'sms_card_notify') {
success_message = 'New card registered to ' + format_phone_number(payload.phone_number) + '. Membership already paid '+ membershipOrder.product.start_date +'. Activation SMS sent ';
}
set_toast(success_message + ' :-)', 'success');
resetCardForm(true);
@@ -477,8 +521,12 @@ $(document).ready(function(){
}).fail(function(data) {
console.log("failed", data);
var error_text = data.responseText;
if(data.responseJSON) {
error_text = data.responseJSON.error;
if (data.responseJSON) {
if (data.responseJSON.non_field_errors) {
error_text = data.responseJSON.non_field_errors.join('<br>');
} else {
error_text = 'Not sure why. :-(';
}
}
set_toast('Failed! '+ error_text, 'error');
});
@@ -487,35 +535,44 @@ $(document).ready(function(){
/* Membership renewal button */
_dom.membershipSubmitButton.on('click', function(e) {
e.preventDefault();
if(!cardForm.fields.phoneNumber || selectedUser.card_number_active === '') {
set_toast('Either users phone number is not valid or user does not have a card registered.', 'error');
if (!selectedUser && !membershipOrder) {
set_toast('Computer says no.', 'error');
return;
}
var payload = getFormData(_dom.registerCardForm);
if( pendingSMSMembership !== null) {
payload.purchased = pendingSMSMembership.purchase_date;
}
else if( selectedUser.expires && moment(selectedUser.expires) > moment() ) {
payload.purchased = selectedUser.expires; // future purchase date
/* If order, make sure to bring along both card number and phone number */
if (membershipOrder) {
payload.card_number = membershipOrder.member_card;
payload.phone_number = membershipOrder.phone_number;
}
$.ajax(urls.renewMembership, {
data: JSON.stringify(payload),
contentType: 'application/json',
dataType: 'json',
type: 'post',
headers: {'X-CSRFToken': getCookie('csrftoken')}
}).success(function(data) {
var full_name = data.user[0].firstname + ' ' + data.user[0].lastname;
var success_message = 'Membership renewed for ' + full_name + ' ';
set_toast(success_message + ' :-)', 'success');
}).done(function(data) {
var name = 'unknown';
if (data.user) {
name = selectedUser.first_name + ' ' + selectedUser.last_name;
} else {
name = 'phone number ' + data.phone_number;
}
var success_message = 'Membership renewed for ' + name + ' :-)';
set_toast(success_message, 'success');
resetCardForm(true);
}).fail(function(data) {
console.log("failed", data);
var error_text = data.responseText;
if(data.responseJSON) {
error_text = data.responseJSON.error;
if (data.responseJSON) {
if (data.responseJSON.non_field_errors) {
error_text = data.responseJSON.non_field_errors.join('<br>');
} else {
error_text = 'Not sure why. :-(';
}
}
set_toast('Failed! '+ error_text, 'error');
});
@@ -526,6 +583,11 @@ $(document).ready(function(){
/* Render selected user in search form */
render_selected_user(selectedUser);
});
/* On order uuid change */
_dom.orderUuidField.on('change', function() {
/* Render selected order */
render_selected_order(membershipOrder);
});
/* Reset buttons */
_dom.registerResetButton.on('click', function() {
resetCardForm();
@@ -541,7 +603,7 @@ $(document).ready(function(){
/* Render initial placeholder user */
var selected_user_html = nunjucksEnv.render('search_result.html', {placeholder: true});
_dom.selectedUserWrap.html(selected_user_html);
_dom.selectedEntityWrap.html(selected_user_html);
/* Initial focus */
if(_dom.usernameField) {
+7 -2
View File
@@ -70,7 +70,7 @@ input[type=number] {
.glyphicon-phone-large {
font-size: 26px;
}
.register-card-form--selected-user-wrap {
.register-card-form--selected-entity-wrap {
min-height: 78px;
margin-bottom: 10px;
}
@@ -86,6 +86,11 @@ input[type=number] {
}
#membership-submit-btn:not(:disabled) {
background-color: #dff0d8;
border-color: #3c763d;
}
.query-form {
margin-bottom: 20px;
}
@@ -111,7 +116,7 @@ input[type=number] {
.label-card-yes {
color: $green-color;
}
.label-is-active {
.label-is-volunteer {
background-color: $active-color;
}
[type=radio] {
+12 -12
View File
@@ -1,29 +1,29 @@
{% if not placeholder %}
<label for="{{ res.id }}" data-phone-number="{{ res.number }}" data-user-id="{{ res.id }}" class="list-group-item search-result can-register-card{% if res.is_member == "1" %} member{% else %} not-member{% endif %}">
<label for="{{ res.id }}" data-phone-number="{{ res.phone_number }}" data-user-id="{{ res.id }}" class="list-group-item search-result can-register-card{% if res.is_member %} member{% else %} not-member{% endif %}">
<input type="radio" name="user" id="{{ res.id }}"{% if checked %} checked{% endif %}>
<div class="name">
<span class="glyphicon glyphicon-user"></span> {{ res.firstname }} {{ res.lastname }}
<span class="glyphicon glyphicon-user"></span> {{ res.first_name }} {{ res.last_name }}
</div>
<!-- Phone number -->
<div class="phone-number"><span class="glyphicon glyphicon-phone"></span> {{ res.number|phoneNumber }}</div>
<div class="phone-number"><span class="glyphicon glyphicon-phone"></span> {{ res.phone_number|phoneNumber }}</div>
<div class="labels">
<!-- Membership -->
{% if res.is_member == "1" %}
<span class="label label-success">Member: {{res.expires|default('Lifelong')}}</span>
{% elif res.expires == "0000-00-00" %}
{% if res.is_member == true %}
<span class="label label-success">Member: {{res.last_membership.end_date or 'Lifelong'}}</span>
{% elif res.last_membership == null %}
<span class="label label-default">Registered</span>
{% else %}
<span class="label label-warning">Expired: {{ res.expires }}</span>
<span class="label label-warning">Expired: {{ res.last_membership.end_date }}</span>
{% endif %}
<!-- Card -->
{% if res.card_number_active and res.card_is_legacy != '' %}
<span class="label label-card label-card-yes"><span class="card-icon"></span> {{ res.card_number_active }}</span>
{% if res.active_member_card %}
<span class="label label-card label-card-yes"><span class="card-icon"></span> {{ res.active_member_card.card_number }}</span>
{% else %}
<span class="label label-card label-card-no"><span class="card-icon"></span> No card</span>
{% endif %}
<!-- Is active -->
{% if res.is_active == "1" %}
<span class="label label-is-active">Active</span>
<!-- Is volunteer -->
{% if res.is_volunteer %}
<span class="label label-is-volunteer">Volunteer</span>
{% endif %}
</div>
{% if not no_user_actions %}
+21
View File
@@ -0,0 +1,21 @@
<div class="list-group-item search-result entity-type-order">
<div class="name">
<input type="radio" name="user" checked>
<span class="glyphicon glyphicon-user"></span> <em>Unregistered member</em>
</div>
<div class="phone-number"><span class="glyphicon glyphicon-phone"></span> {{ order.phone_number|phoneNumber }}</div>
<div class="labels">
<!-- Membership -->
{% if order.product.is_valid == true %}
<span class="label label-success">Member: {{ order.product.end_date or 'Lifelong' }}</span>
{% else %}
<span class="label label-warning">Expired: {{ order.product.end_date }}</span>
{% endif %}
<!-- Card -->
{% if order.member_card %}
<span class="label label-card label-card-yes"><span class="card-icon"></span> {{ order.member_card }}</span>
{% else %}
<span class="label label-card label-card-no"><span class="card-icon"></span> No card</span>
{% endif %}
</div>
</div>
+185 -123
View File
@@ -20,20 +20,7 @@ function getParameterByName(name) {
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
function update_user_with_card_details(user) {
/* Cards with a number lower than this is legacy as of 2015-08 */
var legacy_card_limit = 100000000;
for(var j=0; j<user.cards.length; j++) {
if(parseInt(user.cards[j].card_number, 10) < legacy_card_limit) {
user.cards[j].is_legacy = "1";
}
if(user.cards[j].is_active == "1") {
user.card_number_active = user.cards[j].card_number;
}
}
return user;
}
function format_phone_number(str) {
if(!str || str.length <= 1) {
@@ -46,20 +33,36 @@ function format_phone_number(str) {
return str;
}
function sort_search_results(results) {
return _.sortBy(results, function (x) {
if (x.last_membership) {
if (x.last_membership.end_date) {
return 0 - moment(x.last_membership.end_date).unix();
}
return 0 - moment(x.last_membership.start_date).unix();
}
return 0;
});
}
function format_results(results) {
results = _.map(results, update_user_with_card_details);
return nunjucksEnv.render('search_results.html', {'results': results});
}
function render_selected_user(user_data) {
var context;
if(user_data === undefined || user_data === null) {
if (!user_data) {
context = {placeholder: true}; // dummy card
} else {
user_data = update_user_with_card_details(user_data);
context = {res: user_data, checked: true, no_user_actions: true};
}
var selected_user_html = nunjucksEnv.render('search_result.html', context);
_dom.selectedUserWrap.html(selected_user_html);
_dom.selectedEntityWrap.html(selected_user_html);
}
function render_selected_order(order) {
if (!order) { return; }
var context = {order: order, checked: true, no_user_actions: true};
var selected_order_html = nunjucksEnv.render('selected_order.html', context);
_dom.selectedEntityWrap.html(selected_order_html);
}
function set_toast(message, message_type) {
var context = {
@@ -76,15 +79,9 @@ function set_toast(message, message_type) {
}
function set_selected_user(user, update_search_result) {
selectedUser = user;
var new_val = '';
updateMemberShipButton(user, null);
if(user) {
new_val = user.id;
}
updateMemberShipButton(user);
_dom.userIdField.val(new_val).trigger('change');
_dom.userIdField.val((user && user.id) || '').trigger('change');
if(update_search_result) {
var stale_search_result = _dom.results.find('.search-result').removeClass('selected');
stale_search_result.find('input').prop('checked', false);
@@ -95,6 +92,13 @@ function set_selected_user(user, update_search_result) {
}
}
}
function set_selected_order(order) {
membershipOrder = order;
if (order) {
updateMemberShipButton(null, order);
}
_dom.orderUuidField.val((order && order.uuid) || '').trigger('change');
}
function set_field_state(field_element, state, help_text) {
if(!help_text) {
@@ -140,12 +144,10 @@ function resetCardForm(reset_native) {
set_field_state(_dom.cardNumberField, '');
cardForm.fields.cardNumber = false;
selectedUser = null;
membershipOrder = null;
_dom.userIdField.val('').trigger('change');
_dom.membershipTrialCheckBox.prop('checked', false);
pendingSMSMembership = null;
/* Disable submit buttons */
_dom.registerSubmitButton.prop('disabled', true);
_dom.membershipSubmitButton.prop('disabled', true);
@@ -170,16 +172,34 @@ function update_submit_button() {
}
_dom.registerSubmitButton.prop('disabled', true);
}
function updateMemberShipButton(user) {
var today_plus_one_month = moment().add(1, 'month').format('YYYY-MM-DD');
/* Existing member with upcoming expiry and card */
if(user && user.is_member === '1' && user.expires <= today_plus_one_month && user.card_number_active !== '') {
function updateMemberShipButton(user, order) {
var today_plus_thirty_days = moment().add(30, 'days').format('YYYY-MM-DD');
/* User with valid membership */
if (user && user.is_member) {
/* ...which is lifelong cannot renew */
if (user.last_membership.membership_type == 'lifelong') {
_dom.membershipSubmitButton.prop('disabled', true);
}
/* ...or expiring soon and the user has a card */
//else if (user.last_membership.end_date <= today_plus_thirty_days && user.active_member_card) {
/* ...with a membership that expires soon can renew */
else if (user.last_membership.end_date <= today_plus_thirty_days) {
_dom.membershipSubmitButton.prop('enabled', false);
}
}
/* User with expired membership can renew */
// else if (user && !user.is_member && user.active_member_card) {
else if (user && !user.is_member) {
_dom.membershipSubmitButton.prop('disabled', false);
}
/* User with card, but expired membership */
else if(user && user.is_member !== '1' && user.card_number_active !== '') {
_dom.membershipSubmitButton.prop('disabled', false);
} else {
/* Order */
else if (order) {
/* If the membership has expired it can be renewed */
if (!order.product.is_valid && order.product.end_date <= today_plus_thirty_days) {
_dom.membershipSubmitButton.prop('disabled', false);
}
}
else {
_dom.membershipSubmitButton.prop('disabled', true);
}
}
@@ -187,7 +207,7 @@ function validatePhoneNumber(val) {
if(val.length === 0) {
cardForm.fields.phoneNumber = false;
update_submit_button();
return 'Phonenumber number should not be empty.';
return 'Phone number should not be empty.';
}
cardForm.fields.phoneNumber = true;
update_submit_button();
@@ -204,11 +224,11 @@ function validateCardNumber(val) {
cardForm.fields.cardNumber = false;
update_submit_button();
return 'Card number should start with 1.';
}
}
if(val.length !== 9) {
cardForm.fields.cardNumber = false;
update_submit_button();
return 'Card number should be 9 digits long';
return 'Card number should be 9 digits long.';
}
cardForm.fields.cardNumber = true;
update_submit_button();
@@ -218,8 +238,10 @@ function validateCardNumber(val) {
function getFormData(formElement) {
var formData = formElement.serializeArray();
formData = _.object(_.map(formData, function (x) {
return [x.name, x.value];
return [x.name, x.value || null];
}));
/* no need for radio button state */
delete formData.user;
return formData;
}
@@ -231,44 +253,50 @@ function checkPhoneNumber() {
/* Invalid phone number?*/
if(data.error) {
set_selected_user(null, true);
set_selected_order(null);
set_field_state(_dom.phoneNumberField, 'error', data.error);
cardForm.fields.phoneNumber = false;
pendingSMSMembership = null;
update_submit_button();
return;
}
/* Already has valid card membership? */
if(data.inside.card && data.inside.card.has_valid_membership == "1") {
var card = data.inside.card;
var msg = 'Phone number is already tied to card '+ card.card_number + ' and is valid until '+ card.expires +'.';
set_field_state(_dom.phoneNumberField, 'error', msg);
var user = data.user;
var order = data.order;
var message = '';
/* Has valid card membership */
if (!user && order && order.member_card && order.product.is_valid) {
message = 'Phone number is already tied to card '+ order.member_card + ' and is valid until '+ order.product.end_date +'.';
set_field_state(_dom.phoneNumberField, 'error', message);
cardForm.fields.phoneNumber = false;
pendingSMSMembership = null;
set_selected_order(order);
update_submit_button();
return;
}
var inside = data.inside;
var tekstmelding = data.tekstmelding;
var _user = null;
var _success_msg = '';
if(inside.users.length == 1) {
if (user) {
/* Existing user */
_user = inside.users[0];
_success_msg = 'Phone number belongs to existing user.';
pendingSMSMembership = null;
} else if(tekstmelding.result !== null) {
/* Pending SMS membership (not activated yet) */
_success_msg = 'Phone number has valid membership (paid via SMS). OK to give out card.';
pendingSMSMembership = tekstmelding.result;
message = 'Phone number belongs to existing user.';
set_field_state(_dom.phoneNumberField, 'success', message);
set_selected_user(user, true);
set_selected_order(null);
cardForm.fields.phoneNumber = true;
update_submit_button();
} else if (order) {
set_selected_order(order);
/* Valid membership, no card associated */
if (order.product.is_valid) {
message = 'Phone number has a valid membership.';
} else {
message = 'Phone number has an expired membership.';
}
} else {
/* Valid phone number with no existing user, card membership or pending SMS membership */
pendingSMSMembership = null;
/* Valid phone number associated user or order */
set_selected_order(null);
set_selected_user(null);
}
set_field_state(_dom.phoneNumberField, 'success', _success_msg);
set_selected_user(_user, true);
set_field_state(_dom.phoneNumberField, 'success', message);
cardForm.fields.phoneNumber = true;
update_submit_button();
});
@@ -277,8 +305,9 @@ function checkPhoneNumber() {
/* Init and global vars */
var _dom;
var users;
var searching; /* search ajax request */
var selectedUser;
var pendingSMSMembership;
var membershipOrder; /* selected order object */
var cardForm = {
fields: {
cardNumber: false,
@@ -295,9 +324,9 @@ var nunjucksEnv = new nunjucks.Environment();
nunjucksEnv.addFilter('phoneNumber', format_phone_number);
var urls = {
insideUserApi: '/inside/user/',
checkPhoneNumber: '/check-phonenumber/',
insideCard: '/inside/card/',
userSearch: '/user-search/',
checkPhoneNumber: '/check-phone-number/',
checkCard: '/check-card/',
registerCardAndMembership: '/register-card-membership/',
renewMembership: '/renew-membership/'
};
@@ -315,7 +344,8 @@ $(document).ready(function(){
registerSubmitButton: $('#register-submit-btn'),
usernameField: $('#id_username'),
userIdField: $('#id_user_id'),
selectedUserWrap: $('.register-card-form--selected-user-wrap'),
orderUuidField: $('#id_order_uuid'),
selectedEntityWrap: $('.register-card-form--selected-entity-wrap'),
toastWrap: $('.toast-wrap'),
registerResetButton: $('.register-reset-btn'),
searchResetButton: $('.search-reset-btn'),
@@ -339,39 +369,48 @@ $(document).ready(function(){
/* Card number should exist in the database and not tied to existing user */
var val = _dom.cardNumberField.val().trim();
var validation_msg = validateCardNumber(val);
if( validation_msg !== '' ) {
if (validation_msg !== '') {
set_field_state(_dom.cardNumberField, 'error', validation_msg);
return;
}
$.getJSON(urls.insideCard, {card_number: val}, function(data) {
if(data.error) {
$.getJSON(urls.checkCard, {card_number: val}, function(data) {
if (data.error) {
update_submit_button();
cardForm.fields.cardNumber = false;
set_field_state(_dom.cardNumberField, 'error', data.error);
return;
}
var card = data.card;
var user = data.user;
var today_plus_one_month = moment().add(1, 'month');
if(!card) {
var card = data;
var user = data.user;
var order = data.order;
var today_plus_thirty_days = moment().add(30, 'days');
if (!card) {
cardForm.fields.cardNumber = false;
set_field_state(_dom.cardNumberField, 'error', 'Cannot find card number in database.');
set_field_state(_dom.cardNumberField, 'error',
'Cannot find card number in database.');
}
else if(card && card.registered !== "" && user === null && moment(card.expires) <= today_plus_one_month) {
/* An allready registered card (with no user) can repurchase membership if expired */
else if (card && card.registered !== null && user === null && order && !order.product.is_valid) {
/* An already registered card (with no user) can repurchase membership if expired */
set_selected_order(order);
cardForm.fields.cardNumber = true;
set_field_state(_dom.cardNumberField, 'success', 'Card number is in use (not activated) and belongs to ' + format_phone_number(card.owner_phone_number) + '.');
set_field_state(_dom.cardNumberField, 'success',
'Card number is in use (not activated) and belongs to ' + format_phone_number(order.phone_number) + '.');
}
else if(card && card.registered !== "") {
var owner_string = 'phone number: '+ format_phone_number(card.owner_phone_number);
if(user) {
owner_string = 'existing user: ' + user.firstname + ' ' + user.lastname + ' (' + format_phone_number(user.number) + ')';
else if (card && card.registered !== null) {
var owner_string = '???';
if (user) {
owner_string = 'existing user: ' + user.first_name + ' ' + user.last_name + ' (' + format_phone_number(user.phone_number) + ')';
} else {
set_selected_order(order);
owner_string = 'phone number: '+ format_phone_number(order.phone_number);
}
cardForm.fields.cardNumber = false;
set_field_state(_dom.cardNumberField, 'error', 'Card number is in use and belongs to '+ owner_string +'.');
set_field_state(_dom.cardNumberField, 'error',
'Card number is in use and belongs to '+ owner_string +'.');
}
else if(card && user === null) {
else if (card && user === null) {
cardForm.fields.cardNumber = true;
set_field_state(_dom.cardNumberField, 'success');
} else {
@@ -379,6 +418,10 @@ $(document).ready(function(){
set_field_state(_dom.cardNumberField, '');
}
update_submit_button();
}).fail(function(data) {
cardForm.fields.cardNumber = false;
set_field_state(_dom.cardNumberField, 'error',
'Cannot find card number in database.');
});
});
@@ -389,17 +432,21 @@ $(document).ready(function(){
_dom.results.html('');
return;
}
$.getJSON(urls.insideUserApi, {q: val}, function(data) {
if(data.results && data.results.length > 0) {
_dom.results.html(format_results(data.results));
if (searching) {
searching.abort();
}
searching = $.getJSON(urls.userSearch, {search: val}, function(data) {
if (data.results && data.results.length > 0) {
sorted_results = sort_search_results(data.results);
_dom.results.html(format_results(sorted_results));
users = data.results;
}
else if(data.error) {
else if (data.error) {
_dom.results.html(data.error);
}
else {
if(val !== "") {
_dom.results.html("Found no existing user with search param: '"+ val +"'");
_dom.results.html("Found no user matching: '"+ val +"'");
} else {
_dom.results.html('');
}
@@ -420,7 +467,7 @@ $(document).ready(function(){
set_selected_user(_.findWhere(users, {id: user_id}));
/* Update phone number field */
if(number === '-') {
if (number === '-') {
number = '';
}
_dom.phoneNumberField.val(number).trigger('input');
@@ -438,13 +485,13 @@ $(document).ready(function(){
/* Register type */
// FIXME: get from form
// action: 'new_card_membership', 'update_card', 'add_or_renew', 'sms_card_notify'
if(selectedUser === null && pendingSMSMembership === null) {
if (selectedUser === null && membershipOrder === null) {
payload.action = 'new_card_membership';
} else if(selectedUser === null && pendingSMSMembership !== null) {
} else if (selectedUser === null && membershipOrder !== null) {
payload.action = 'sms_card_notify';
payload.purchased = pendingSMSMembership.purchase_date;
payload.order_uuid = membershipOrder.uuid;
} else {
if(selectedUser.is_member === '1') {
if (selectedUser.is_member) {
payload.action = 'update_card';
} else {
payload.action = 'add_or_renew'; // Note: could also update card number
@@ -456,20 +503,17 @@ $(document).ready(function(){
dataType: 'json',
type: 'post',
headers: {'X-CSRFToken': getCookie('csrftoken')}
}).success(function(data){
var phone_number;
}).done(function(data) {
var success_message = 'New card registered ' + payload.card_number;
if(payload.action === 'add_or_renew') {
var full_name = data.user[0].firstname + ' ' + data.user[0].lastname;
if (payload.action === 'add_or_renew') {
var full_name = selectedUser.first_name + ' ' + selectedUser.last_name;
success_message = 'New membership and card number '+ payload.card_number + ' registered to ' + full_name + ' ';
}
else if(payload.action ==='new_card_membership') {
phone_number = data.card.owner_phone_number;
success_message = 'New membership and card registered to ' + format_phone_number(phone_number) + '. Activation SMS sent ';
else if (payload.action ==='new_card_membership') {
success_message = 'New membership and card registered to ' + format_phone_number(payload.phone_number) + '. Activation SMS sent ';
}
else if(payload.action === 'sms_card_notify') {
phone_number = data.card.owner_phone_number;
success_message = 'New card registered to ' + format_phone_number(phone_number) + '. Membership already paid by SMS ('+ pendingSMSMembership.purchase_date +'). Activation SMS sent ';
else if (payload.action === 'sms_card_notify') {
success_message = 'New card registered to ' + format_phone_number(payload.phone_number) + '. Membership already paid '+ membershipOrder.product.start_date +'. Activation SMS sent ';
}
set_toast(success_message + ' :-)', 'success');
resetCardForm(true);
@@ -477,8 +521,12 @@ $(document).ready(function(){
}).fail(function(data) {
console.log("failed", data);
var error_text = data.responseText;
if(data.responseJSON) {
error_text = data.responseJSON.error;
if (data.responseJSON) {
if (data.responseJSON.non_field_errors) {
error_text = data.responseJSON.non_field_errors.join('<br>');
} else {
error_text = 'Not sure why. :-(';
}
}
set_toast('Failed! '+ error_text, 'error');
});
@@ -487,35 +535,44 @@ $(document).ready(function(){
/* Membership renewal button */
_dom.membershipSubmitButton.on('click', function(e) {
e.preventDefault();
if(!cardForm.fields.phoneNumber || selectedUser.card_number_active === '') {
set_toast('Either users phone number is not valid or user does not have a card registered.', 'error');
if (!selectedUser && !membershipOrder) {
set_toast('Computer says no.', 'error');
return;
}
var payload = getFormData(_dom.registerCardForm);
if( pendingSMSMembership !== null) {
payload.purchased = pendingSMSMembership.purchase_date;
}
else if( selectedUser.expires && moment(selectedUser.expires) > moment() ) {
payload.purchased = selectedUser.expires; // future purchase date
/* If order, make sure to bring along both card number and phone number */
if (membershipOrder) {
payload.card_number = membershipOrder.member_card;
payload.phone_number = membershipOrder.phone_number;
}
$.ajax(urls.renewMembership, {
data: JSON.stringify(payload),
contentType: 'application/json',
dataType: 'json',
type: 'post',
headers: {'X-CSRFToken': getCookie('csrftoken')}
}).success(function(data) {
var full_name = data.user[0].firstname + ' ' + data.user[0].lastname;
var success_message = 'Membership renewed for ' + full_name + ' ';
set_toast(success_message + ' :-)', 'success');
}).done(function(data) {
var name = 'unknown';
if (data.user) {
name = selectedUser.first_name + ' ' + selectedUser.last_name;
} else {
name = 'phone number ' + data.phone_number;
}
var success_message = 'Membership renewed for ' + name + ' :-)';
set_toast(success_message, 'success');
resetCardForm(true);
}).fail(function(data) {
console.log("failed", data);
var error_text = data.responseText;
if(data.responseJSON) {
error_text = data.responseJSON.error;
if (data.responseJSON) {
if (data.responseJSON.non_field_errors) {
error_text = data.responseJSON.non_field_errors.join('<br>');
} else {
error_text = 'Not sure why. :-(';
}
}
set_toast('Failed! '+ error_text, 'error');
});
@@ -526,6 +583,11 @@ $(document).ready(function(){
/* Render selected user in search form */
render_selected_user(selectedUser);
});
/* On order uuid change */
_dom.orderUuidField.on('change', function() {
/* Render selected order */
render_selected_order(membershipOrder);
});
/* Reset buttons */
_dom.registerResetButton.on('click', function() {
resetCardForm();
@@ -541,7 +603,7 @@ $(document).ready(function(){
/* Render initial placeholder user */
var selected_user_html = nunjucksEnv.render('search_result.html', {placeholder: true});
_dom.selectedUserWrap.html(selected_user_html);
_dom.selectedEntityWrap.html(selected_user_html);
/* Initial focus */
if(_dom.usernameField) {
+112 -26
View File
@@ -1,17 +1,19 @@
(function() {(window.nunjucksPrecompiled = window.nunjucksPrecompiled || {})["search_result.html"] = (function() {function root(env, context, frame, runtime, cb) {
(function() {(window.nunjucksPrecompiled = window.nunjucksPrecompiled || {})["search_result.html"] = (function() {
function root(env, context, frame, runtime, cb) {
var lineno = null;
var colno = null;
var output = "";
try {
var parentTemplate = null;
if(!runtime.contextOrFrameLookup(context, frame, "placeholder")) {
output += "\n <label for=\"";
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"id", env.opts.autoescape), env.opts.autoescape);
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"id"), env.opts.autoescape);
output += "\" data-phone-number=\"";
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"number", env.opts.autoescape), env.opts.autoescape);
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"phone_number"), env.opts.autoescape);
output += "\" data-user-id=\"";
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"id", env.opts.autoescape), env.opts.autoescape);
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"id"), env.opts.autoescape);
output += "\" class=\"list-group-item search-result can-register-card";
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"is_member", env.opts.autoescape) == "1") {
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"is_member")) {
output += " member";
;
}
@@ -20,42 +22,42 @@ output += " not-member";
;
}
output += "\">\n <input type=\"radio\" name=\"user\" id=\"";
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"id", env.opts.autoescape), env.opts.autoescape);
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"id"), env.opts.autoescape);
output += "\"";
if(runtime.contextOrFrameLookup(context, frame, "checked")) {
output += " checked";
;
}
output += ">\n <div class=\"name\">\n <span class=\"glyphicon glyphicon-user\"></span> ";
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"firstname", env.opts.autoescape), env.opts.autoescape);
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"first_name"), env.opts.autoescape);
output += " ";
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"lastname", env.opts.autoescape), env.opts.autoescape);
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"last_name"), env.opts.autoescape);
output += "\n </div>\n <!-- Phone number -->\n <div class=\"phone-number\"><span class=\"glyphicon glyphicon-phone\"></span> ";
output += runtime.suppressValue(env.getFilter("phoneNumber").call(context, runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"number", env.opts.autoescape)), env.opts.autoescape);
output += runtime.suppressValue(env.getFilter("phoneNumber").call(context, runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"phone_number")), env.opts.autoescape);
output += "</div>\n <div class=\"labels\">\n <!-- Membership -->\n ";
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"is_member", env.opts.autoescape) == "1") {
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"is_member") == true) {
output += "\n <span class=\"label label-success\">Member: ";
output += runtime.suppressValue(env.getFilter("default").call(context, runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"expires", env.opts.autoescape),"Lifelong"), env.opts.autoescape);
output += runtime.suppressValue(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"last_membership")),"end_date") || "Lifelong", env.opts.autoescape);
output += "</span>\n ";
;
}
else {
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"expires", env.opts.autoescape) == "0000-00-00") {
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"last_membership") == runtime.contextOrFrameLookup(context, frame, "null")) {
output += "\n <span class=\"label label-default\">Registered</span>\n ";
;
}
else {
output += "\n <span class=\"label label-warning\">Expired: ";
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"expires", env.opts.autoescape), env.opts.autoescape);
output += runtime.suppressValue(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"last_membership")),"end_date"), env.opts.autoescape);
output += "</span>\n ";
;
}
;
}
output += "\n <!-- Card -->\n ";
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"card_number_active", env.opts.autoescape) && runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"card_is_legacy", env.opts.autoescape) != "") {
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"active_member_card")) {
output += "\n <span class=\"label label-card label-card-yes\"><span class=\"card-icon\"></span> ";
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"card_number_active", env.opts.autoescape), env.opts.autoescape);
output += runtime.suppressValue(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"active_member_card")),"card_number"), env.opts.autoescape);
output += "</span>\n ";
;
}
@@ -63,20 +65,20 @@ else {
output += "\n <span class=\"label label-card label-card-no\"><span class=\"card-icon\"></span> No card</span>\n ";
;
}
output += "\n <!-- Is active -->\n ";
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"is_active", env.opts.autoescape) == "1") {
output += "\n <span class=\"label label-is-active\">Active</span>\n ";
output += "\n <!-- Is volunteer -->\n ";
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"is_volunteer")) {
output += "\n <span class=\"label label-is-volunteer\">Volunteer</span>\n ";
;
}
output += "\n </div>\n ";
if(!runtime.contextOrFrameLookup(context, frame, "no_user_actions")) {
output += "\n <div class=\"user-actions\">\n <div class=\"btn-group\">\n <!--<a href=\"#\" class=\"btn btn-default";
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"is_member", env.opts.autoescape) == "1") {
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"is_member") == "1") {
output += " disabled";
;
}
output += "\">Membership</a>\n <a href=\"#\" class=\"btn btn-default\">Reg. card</a>-->\n <div class=\"btn-group\" role=\"group\">\n <button type=\"button\" class=\"btn btn-default dropdown-toggle\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n <span class=\"glyphicon glyphicon-option-vertical\"></span>\n </button>\n <ul class=\"dropdown-menu\">\n <li><a href=\"https://inside.studentersamfundet.no/index.php?page=display-user&userid=";
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"id", env.opts.autoescape), env.opts.autoescape);
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"id"), env.opts.autoescape);
output += "\" target=\"_blank\">Edit in Inside</a></li>\n </ul>\n </div>\n </div>\n </div>\n ";
;
}
@@ -89,7 +91,11 @@ output += runtime.suppressValue(env.getFilter("phoneNumber").call(context, runti
output += "</div>\n <div class=\"labels\">\n <span class=\"label label-card\"><span class=\"card-icon\"></span> New card</span>\n </div>\n </div>\n";
;
}
if(parentTemplate) {
parentTemplate.rootRenderFunc(env, context, frame, runtime, cb);
} else {
cb(null, output);
}
;
} catch (e) {
cb(runtime.handleError(e, lineno, colno));
@@ -98,14 +104,17 @@ cb(null, output);
return {
root: root
};
})();
})();
(function() {(window.nunjucksPrecompiled = window.nunjucksPrecompiled || {})["search_results.html"] = (function() {function root(env, context, frame, runtime, cb) {
(function() {(window.nunjucksPrecompiled = window.nunjucksPrecompiled || {})["search_results.html"] = (function() {
function root(env, context, frame, runtime, cb) {
var lineno = null;
var colno = null;
var output = "";
try {
var parentTemplate = null;
output += "<div class=\"list-group\">\n ";
frame = frame.push();
var t_3 = runtime.contextOrFrameLookup(context, frame, "results");
@@ -121,18 +130,36 @@ frame.set("loop.first", t_1 === 0);
frame.set("loop.last", t_1 === t_2 - 1);
frame.set("loop.length", t_2);
output += "\n ";
env.getTemplate("search_result.html", false, "search_results.html", function(t_7,t_5) {
var tasks = [];
tasks.push(
function(callback) {
env.getTemplate("search_result.html", false, "search_results.html", null, function(t_7,t_5) {
if(t_7) { cb(t_7); return; }
t_5.render(context.getVariables(), frame.push(), function(t_8,t_6) {
callback(null,t_5);});
});
tasks.push(
function(template, callback){
template.render(context.getVariables(), frame, function(t_8,t_6) {
if(t_8) { cb(t_8); return; }
output += t_6
callback(null,t_6);});
});
tasks.push(
function(result, callback){
output += result;
callback(null);
});
env.waterfall(tasks, function(){
output += "\n ";
})});
});
}
}
frame = frame.pop();
output += "\n</div>";
if(parentTemplate) {
parentTemplate.rootRenderFunc(env, context, frame, runtime, cb);
} else {
cb(null, output);
}
;
} catch (e) {
cb(runtime.handleError(e, lineno, colno));
@@ -141,14 +168,68 @@ cb(null, output);
return {
root: root
};
})();
})();
(function() {(window.nunjucksPrecompiled = window.nunjucksPrecompiled || {})["toast.html"] = (function() {function root(env, context, frame, runtime, cb) {
(function() {(window.nunjucksPrecompiled = window.nunjucksPrecompiled || {})["selected_order.html"] = (function() {
function root(env, context, frame, runtime, cb) {
var lineno = null;
var colno = null;
var output = "";
try {
var parentTemplate = null;
output += "<div class=\"list-group-item search-result entity-type-order\">\n <div class=\"name\">\n <input type=\"radio\" name=\"user\" checked>\n <span class=\"glyphicon glyphicon-user\"></span> <em>Unregistered member</em>\n </div>\n <div class=\"phone-number\"><span class=\"glyphicon glyphicon-phone\"></span> ";
output += runtime.suppressValue(env.getFilter("phoneNumber").call(context, runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "order")),"phone_number")), env.opts.autoescape);
output += "</div>\n <div class=\"labels\">\n <!-- Membership -->\n ";
if(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "order")),"product")),"is_valid") == true) {
output += "\n <span class=\"label label-success\">Member: ";
output += runtime.suppressValue(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "order")),"product")),"end_date") || "Lifelong", env.opts.autoescape);
output += "</span>\n ";
;
}
else {
output += "\n <span class=\"label label-warning\">Expired: ";
output += runtime.suppressValue(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "order")),"product")),"end_date"), env.opts.autoescape);
output += "</span>\n ";
;
}
output += "\n <!-- Card -->\n ";
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "order")),"member_card")) {
output += "\n <span class=\"label label-card label-card-yes\"><span class=\"card-icon\"></span> ";
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "order")),"member_card"), env.opts.autoescape);
output += "</span>\n ";
;
}
else {
output += "\n <span class=\"label label-card label-card-no\"><span class=\"card-icon\"></span> No card</span>\n ";
;
}
output += "\n </div>\n</div>\n";
if(parentTemplate) {
parentTemplate.rootRenderFunc(env, context, frame, runtime, cb);
} else {
cb(null, output);
}
;
} catch (e) {
cb(runtime.handleError(e, lineno, colno));
}
}
return {
root: root
};
})();
})();
(function() {(window.nunjucksPrecompiled = window.nunjucksPrecompiled || {})["toast.html"] = (function() {
function root(env, context, frame, runtime, cb) {
var lineno = null;
var colno = null;
var output = "";
try {
var parentTemplate = null;
output += "<span class=\"toast-";
output += runtime.suppressValue(runtime.contextOrFrameLookup(context, frame, "message_type"), env.opts.autoescape);
output += "\"><span class=\"glyphicon glyphicon-";
@@ -156,7 +237,11 @@ output += runtime.suppressValue(runtime.contextOrFrameLookup(context, frame, "ic
output += "\"></span> ";
output += runtime.suppressValue(runtime.contextOrFrameLookup(context, frame, "message"), env.opts.autoescape);
output += "</span>";
if(parentTemplate) {
parentTemplate.rootRenderFunc(env, context, frame, runtime, cb);
} else {
cb(null, output);
}
;
} catch (e) {
cb(runtime.handleError(e, lineno, colno));
@@ -165,5 +250,6 @@ cb(null, output);
return {
root: root
};
})();
})();
+9524 -6916
View File
File diff suppressed because it is too large. Load diff
+10 -1
View File
@@ -1 +1,10 @@
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0}mark{background:#ff0;color:#000}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}hr{box-sizing:content-box;height:0}pre{overflow:auto}samp{font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*,*:before,*:after{background:transparent!important;color:#000!important;box-shadow:none!important;text-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url("../fonts/glyphicons-halflings-regular.eot");src:url("../fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/glyphicons-halflings-regular.woff2") format("woff2"),url("../fonts/glyphicons-halflings-regular.woff") format("woff"),url("../fonts/glyphicons-halflings-regular.ttf") format("truetype"),url("../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.gLine truncated
@charset "UTF-8";
/*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}legend,td,th{padding:0}
/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */
@media print{*,:after,:before{background:0 0!important;color:#000!important;box-shadow:none!important;text-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{border:1px solid #999}thead{display:table-header-group}blockquote,img,pre,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format("embedded-opentype"),url(../fonts/glyphicons-halflings-regular.woff2) format("woff2"),url(../fonts/glyphicons-halflings-regular.woff) format("woff"),url(../fonts/glyphicons-halflings-regular.ttf) format("truetype"),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\Line truncated
+1 -1
View File
@@ -116,7 +116,7 @@ gulp.task('serve', ['styles'], function () {
gulp.task('watch', ['serve'], function () {
browserSync.init({
proxy: 'localhost:8000'
proxy: 'localhost:8001'
});
// watch for changes
+4449
View File
File diff suppressed because it is too large. Load diff