This repository has been archived on 2026-10-23. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
kassa/static/app/scripts/app.js
T

635 lines
23 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
function format_phone_number(str) {
if(!str || str.length <= 1) {
return 'No phone number';
}
if(str.length == 11) {
return str.slice(0, 3) + " " + str.slice(3, 6) + " " + str.slice(6, 8) + " " + str.slice(8);
}
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) {
return nunjucksEnv.render('search_results.html', {'results': results});
}
function render_selected_user(user_data) {
var context;
if (!user_data) {
context = {placeholder: true}; // dummy card
} else {
context = {res: user_data, checked: true, no_user_actions: true};
}
var selected_user_html = nunjucksEnv.render('search_result.html', context);
_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 = {
message: message,
message_type: message_type,
icon: state_to_icon[message_type]
};
var html = nunjucksEnv.render('toast.html', context);
_dom.toastWrap.html(html);
_dom.toastWrap.addClass('visible');
setTimeout(function() {
_dom.toastWrap.removeClass('visible');
}, 5000);
}
function set_selected_user(user, update_search_result) {
selectedUser = user;
updateMemberShipButton(user, null);
_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);
/* User is still in search result, mark selected */
if( user && _.findWhere(users, {id:user.id}) ) {
var search_result = _dom.results.find('[data-user-id="'+ user.id +'"]').toggleClass('selected');
search_result.find('input').prop('checked', true);
}
}
}
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) {
help_text = '';
}
var help_span = '<span class="help-block ">'+ help_text +'</span>';
var feedback_span = '<span class="glyphicon glyphicon-'+ state_to_icon[state] +' form-control-feedback" aria-hidden="true"></span>';
var css_class = 'has-'+state;
var form_group = field_element.parent().parent();
form_group.removeClass('has-success has-warning has-error has-feedback'); // reset
if(['success', 'warning', 'error'].indexOf(state) !== -1) {
form_group.addClass(css_class +' has-feedback');
/* Feedback icon */
var feedback_el = form_group.find('.form-control-feedback');
if (!feedback_el.length) {
// add
form_group.append(feedback_span);
} else {
// Update
feedback_el.removeClass('glyphicon-ok glypicon-warning-sign glyphicon-remove');
feedback_el.addClass('glyphicon-'+state_to_icon[state]);
}
/* Help text */
var help_text_el = form_group.find('.help-block');
if(!help_text_el.length) {
form_group.append(help_span);
} else {
help_text_el.text(help_text);
}
}
else {
form_group.find('.form-control-feedback').remove();
form_group.find('.help-block').remove();
}
}
function resetCardForm(reset_native) {
if(reset_native) {
_dom.registerCardForm.get(0).reset();
}
/* Clear field states */
set_field_state(_dom.phoneNumberField, '');
cardForm.fields.phoneNumber = false;
set_field_state(_dom.cardNumberField, '');
cardForm.fields.cardNumber = false;
selectedUser = null;
membershipOrder = null;
_dom.userIdField.val('').trigger('change');
_dom.membershipTrialCheckBox.prop('checked', false);
/* Disable submit buttons */
_dom.registerSubmitButton.prop('disabled', true);
_dom.membershipSubmitButton.prop('disabled', true);
}
function resetSearchForm(reset_native) {
if(reset_native) {
_dom.searchResetButton.get(0).reset();
}
/* Clear search results */
users = null;
_dom.results.html('');
}
function cardFormIsValid() {
return _.all(_.values(cardForm.fields));
}
function update_submit_button() {
if( cardFormIsValid() ) {
_dom.registerSubmitButton.prop('disabled', false);
return;
}
_dom.registerSubmitButton.prop('disabled', true);
}
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('disabled', 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);
}
/* 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);
}
}
function validatePhoneNumber(val) {
if(val.length === 0) {
cardForm.fields.phoneNumber = false;
update_submit_button();
return 'Phone number should not be empty.';
}
cardForm.fields.phoneNumber = true;
update_submit_button();
return '';
}
function validateCardNumber(val) {
if(val.length === 0) {
cardForm.fields.cardNumber = false;
update_submit_button();
return 'Card number should not be empty.';
}
if(val.length > 0 && val[0] != "1") {
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.';
}
cardForm.fields.cardNumber = true;
update_submit_button();
return '';
}
function getFormData(formElement) {
var formData = formElement.serializeArray();
formData = _.object(_.map(formData, function (x) {
return [x.name, x.value || null];
}));
/* no need for radio button state */
delete formData.user;
return formData;
}
/* Phone number as you type */
function checkPhoneNumber() {
var val = _dom.phoneNumberField.val().trim();
$.getJSON(urls.checkPhoneNumber, {phone_number: val}, function(data) {
/* 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;
update_submit_button();
return;
}
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;
set_selected_order(order);
update_submit_button();
return;
}
if (user) {
/* Existing user */
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 associated user or order */
set_selected_order(null);
set_selected_user(null);
}
set_field_state(_dom.phoneNumberField, 'success', message);
cardForm.fields.phoneNumber = true;
update_submit_button();
});
}
/* Init and global vars */
var _dom;
var users;
var searching; /* search ajax request */
var selectedUser = null;
var membershipOrder = null; /* selected order object */
var cardForm = {
fields: {
cardNumber: false,
phoneNumber: false
}
};
var state_to_icon = {
success: 'ok',
warning: 'warning-sign',
error: 'remove'
};
var nunjucksEnv = new nunjucks.Environment();
/* Add filter |phoneNumber */
nunjucksEnv.addFilter('phoneNumber', format_phone_number);
var urls = {
userSearch: '/user-search/',
checkPhoneNumber: '/check-phone-number/',
checkCard: '/check-card/',
registerCardAndMembership: '/register-card-membership/',
renewMembership: '/renew-membership/'
};
$(document).ready(function(){
_dom = {
results: $('.results-wrap'),
query: $('#id_query'),
phoneNumberLabel: $('[for=id_phone_number]'),
phoneNumberField: $('#id_phone_number'),
cardNumberField: $('#id_card_number'),
registerCardForm: $('.register-card-form'),
registerSubmitButton: $('#register-submit-btn'),
usernameField: $('#id_username'),
userIdField: $('#id_user_id'),
orderUuidField: $('#id_order_uuid'),
selectedEntityWrap: $('.register-card-form--selected-entity-wrap'),
toastWrap: $('.toast-wrap'),
registerResetButton: $('.register-reset-btn'),
searchResetButton: $('.search-reset-btn'),
membershipSubmitButton: $('#membership-submit-btn'),
membershipTrialCheckBox: $('#id_membership_trial')
};
var lazyCheckPhoneNumber = _.debounce(checkPhoneNumber, 250);
_dom.phoneNumberField.on('input', function() {
var val = _dom.phoneNumberField.val().trim();
var validation_msg = validatePhoneNumber(val);
if( validation_msg !== '') {
set_selected_user(null, true);
set_field_state(_dom.phoneNumberField, 'error', validation_msg);
return;
}
lazyCheckPhoneNumber();
});
/* Card number as you type */
_dom.cardNumberField.on('input', 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 !== '') {
set_field_state(_dom.cardNumberField, 'error', validation_msg);
return;
}
$.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;
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.');
}
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(order.phone_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 +'.');
}
else if (card && user === null) {
cardForm.fields.cardNumber = true;
set_field_state(_dom.cardNumberField, 'success');
} else {
cardForm.fields.cardNumber = true;
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.');
});
});
/* User search as you type */
_dom.query.on('input', function() {
var val = _dom.query.val().trim();
if(val.length <= 2) {
_dom.results.html('');
return;
}
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) {
_dom.results.html(data.error);
}
else {
if(val !== "") {
_dom.results.html("Found no user matching: '"+ val +"'");
} else {
_dom.results.html('');
}
}
});
});
/* On search result click */
_dom.results.on('click', '.search-result input', function(e){
var label = $(this).parent();
var number = label.attr('data-phone-number');
var user_id = label.attr('data-user-id');
/* Update selected state */
$('.search-result').removeClass('selected');
label.toggleClass('selected');
set_selected_user(_.findWhere(users, {id: user_id}));
/* Update phone number field */
if (number === '-') {
number = '';
}
_dom.phoneNumberField.val(number).trigger('input');
});
/* On submit button click */
_dom.registerSubmitButton.on('click', function(e) {
e.preventDefault();
if( !cardFormIsValid() ) {
set_toast('Either phone number or card number is not valid', 'error');
return;
}
var payload = getFormData(_dom.registerCardForm);
/* Register type */
// FIXME: get from form
// action: 'new_card_membership', 'update_card', 'add_or_renew', 'sms_card_notify'
if (selectedUser === null && membershipOrder === null) {
// Unknown phone / card, new membership
payload.action = 'new_card_membership';
} else if (selectedUser === null && membershipOrder !== null) {
if (membershipOrder.member_card === null && membershipOrder.product.is_valid) {
// Associate member card with a valid membership order
payload.action = 'sms_card_notify';
payload.order_uuid = membershipOrder.uuid;
} else {
// New card for expired order -- treat as new card membership
payload.action = 'new_card_membership';
}
} else if (selectedUser !== null && selectedUser.is_member) {
// Add member card to a user
payload.action = 'update_card';
} else {
// New order for user
payload.action = 'add_or_renew'; // Note: could also update card number
}
$.ajax(urls.registerCardAndMembership, {
data: JSON.stringify(payload),
contentType: 'application/json',
dataType: 'json',
type: 'post',
headers: {'X-CSRFToken': getCookie('csrftoken')}
}).done(function(data) {
var success_message = 'New card registered ' + payload.card_number;
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') {
success_message = 'New membership and card registered to ' + format_phone_number(payload.phone_number) + '. 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);
}).fail(function(data) {
console.log("failed", data);
var error_text = data.responseText;
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');
});
});
/* Membership renewal button */
_dom.membershipSubmitButton.on('click', function(e) {
e.preventDefault();
if (!selectedUser && !membershipOrder) {
set_toast('Computer says no.', 'error');
return;
}
var payload = getFormData(_dom.registerCardForm);
/* 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')}
}).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) {
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');
});
});
/* On user id change */
_dom.userIdField.on('change', 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();
});
_dom.searchResetButton.on('click', function() {
resetSearchForm();
});
/* Ninja add some icons to bootstrap form */
var iconMobile = '<span class="input-group-addon"><span class="glyphicon glyphicon-phone glyphicon-phone-large"></span></span>';
_dom.phoneNumberField.parent().addClass('input-group');
_dom.phoneNumberField.before(iconMobile);
/* Render initial placeholder user */
var selected_user_html = nunjucksEnv.render('search_result.html', {placeholder: true});
_dom.selectedEntityWrap.html(selected_user_html);
/* Initial focus */
if(_dom.usernameField) {
_dom.usernameField.focus();
}
/* Load query params */
if( getParameterByName('user_id') ) {
_dom.userIdField.val(getParameterByName('user_id')).trigger('change');
}
if( getParameterByName('phone_number') ) {
_dom.phoneNumberField.val(getParameterByName('phone_number')).trigger('input');
}
if( getParameterByName('card_number') ) {
_dom.cardNumberField.val(getParameterByName('card_number')).trigger('input');
}
$('[data-toggle="popover"]').popover({
html: true,
trigger: 'click'
});
});