⬆ Bump python deps and upgrade everything

Replace gulp with webpack
Switch to npm from yarn
Add eslint and prettier
This commit is contained in:
nikolark committed 2020-01-05 19:31:17 +01:00
1 parent 45621280bc
commit be9f84d6dd
33 files changed
+11851 -26393

No files matched your search

+2 -1
View File
@@ -6,4 +6,5 @@ db.sqlite3
venv
node_modules
bower_components
staticfiles
staticfiles
static/dist/
+27 -12
View File
@@ -1,17 +1,26 @@
Kassa helps you register purchased membership cards and memberships. It's a tool for volunteers and bar employees at The Norwegian Student Society.
## TODO
* replace nunjucks with something that is maintained
* autoprefixer
* bootstrap 4.x
* Loading state on submit buttons
* Galtinn/LDAP auth
## Installation
apt install libldap2-dev libsasl2-dev # pyldap deps
python3 -m venv venv
. venv/bin/activate
pip install -U pip wheel
pip install -r requirements.txt
python manage.py migrate
```shell script
apt install libldap2-dev libsasl2-dev # pyldap deps
python3 -m venv venv
. venv/bin/activate
pip install -U pip wheel
pip install -r requirements.txt
python manage.py migrate
```
### Development tasks
```shell script
npm run build:templates # regenerate nunjucks templates
```
## Configuration
Create a user in Galtinn with the following permissions:
* galtinn | User | Can view User
@@ -22,14 +31,20 @@ Create a user in Galtinn with the following permissions:
* galtinn | Order | Can view Order
Fetch GALTINN_API_KEY:
```shell script
curl -H "Content-Type: application/json" -X POST -d '{"username":"apiuser","password":"test"}' GALTINN_API_URL/auth/obtain-token/
```
If developing you can disable calls to Tekstmelding in local_settings.py:
TEKSTMELDING_ENABLED = False
```python
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
ldapsearch -x -b dc=neuf,dc=no
# Login with test@example.com:test
```shell script
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
ldapsearch -x -b dc=neuf,dc=no
# Login with test@example.com:test
```
+5
View File
@@ -0,0 +1,5 @@
from django.conf import settings
def sentry(request):
return {'SENTRY_DSN': settings.SENTRY_DSN or '', 'SENTRY_ENVIRONMENT': settings.SENTRY_ENVIRONMENT}
+11 -11
View File
@@ -1,18 +1,14 @@
{% load staticfiles raven %}
{% load static %}
<!DOCTYPE html>
<html>
<head lang="en">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Registrer medlemskap{% endblock %}</title>
<link rel="shortcut icon" href="{% static "dist/images/favicon.ico" %}">
<!--<link rel="stylesheet" href="{% static "dist/styles/vendor.css" %}">-->
<link rel="stylesheet" href="{% static "dist/styles/app.css" %}">
<script src="{% static "dist/scripts/vendor.js" %}"></script>
<script src="{% static "dist/scripts/templates.js" %}"></script>
<script src="{% static "dist/scripts/app.js" %}"></script>
<script src="https://cdn.ravenjs.com/3.5.1/raven.min.js"></script>
<link rel="stylesheet" href="{% static "dist/main.css" %}">
</head>
<body>
<div class="toast-wrap"></div>
@@ -24,11 +20,15 @@
</div>
<footer class="footer">
<div class="container">
<div class="credits"><p><a href="https://studentersamfundet.no" title="Det Norske Studentersamfund"><img src="{% static "dist/images/logo.png" %}" width="70" height="70"></a></p>
<div class="credits"><p><a href="https://studentersamfundet.no" title="Det Norske Studentersamfund"><img src="{% static "dist/images/logo.png" %}" width="70" height="70" alt="DNS logo"></a></p>
Laget med <span class="love" title="kærlighed">♥</span> av <a href="http://kak.studentersamfundet.no/" title="Kommunikasjonsavdelingen">KAK</a></div>
</div>
</footer>
<script>Raven.config('{% sentry_public_dsn %}').install()</script>
{% if SENTRY_DSN %}
<script src="https://browser.sentry-cdn.com/5.10.2/bundle.min.js" integrity="sha384-ssBfXiBvlVC7bdA/VX03S88B5MwXQWdnpJRbUYFPgswlOBwETwTp6F3SMUNpo9M9" crossorigin="anonymous"></script>
<script>Sentry.init({ dsn: '{{ SENTRY_DSN }}', environment: '{{ SENTRY_ENVIRONMENT }}' });</script>
{% endif %}
<script src="{% static "dist/main.js" %}"></script>
</body>
</html>
+8 -7
View File
@@ -1,12 +1,13 @@
from django.conf.urls import url
from django.urls import path, re_path
from apps.kassa.views import (register, user_search, check_card, check_phone_number, register_card_and_membership,
renew_membership)
urlpatterns = [
url(r'^$', register, name='register'),
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'),
re_path(r'^$', register, name='register'),
path('user-search/', user_search, name='user-search'),
path('check-phone-number/', check_phone_number, name='check-phone-number'),
path('check-card/', check_card, name='check-card'),
path('register-card-membership/', register_card_and_membership, name='register-card-and-membership'),
path('renew-membership/', renew_membership, name='renew-membership'),
]
Vendored
+28 -20
View File
@@ -1,25 +1,33 @@
from contextlib import contextmanager as _contextmanager
from fabric.api import run, sudo, env, cd, prefix
import os
env.use_ssh_config = True
env.hosts = ['dreamcast.neuf.no']
env.project_path = '/var/www/neuf.no/kassa'
env.user = 'gitdeploy'
env.activate = 'source {}/venv/bin/activate'.format(env.project_path)
from fabric import Connection
from invoke import task
@_contextmanager
def virtualenv():
with cd(env.project_path), prefix(env.activate):
yield
@task()
def deploy(c):
"""Make sure proxy_user is set to your neuf username."""
project_path = '/var/www/neuf.no/kassa'
proxy_user = os.getenv('DEPLOY_USER', os.getenv('USER'))
c = Connection(host='gitdeploy@dreamcast.neuf.no', gateway=Connection('login.neuf.no', user=proxy_user))
with c.cd(project_path), c.prefix('source {}/venv/bin/activate'.format(project_path)):
c.run('git pull') # Get source
c.run('pip install -U pip')
c.run('pip install -r requirements.txt') # install deps in virtualenv
with c.cd('static'): # install and compile frontend deps
c.run('npm i')
c.run('npm run build')
c.run('python manage.py collectstatic --noinput -i node_modules') # Collect static
c.run('python manage.py migrate') # Run DB migrations
# Reload gunicorn
c.sudo('/usr/bin/supervisorctl pid kassa.neuf.no | xargs kill -HUP', shell=False)
def deploy():
with virtualenv():
run('git pull') # Get source
run('pip install -r requirements.txt') # install deps in virtualenv
run('umask 022; python manage.py collectstatic --noinput') # Collect static
run('python manage.py migrate') # Run DB migrations
# Reload
sudo('/usr/bin/supervisorctl pid kassa.neuf.no | xargs kill -HUP', shell=False)
@task
def build(c):
with c.cd('static'):
c.run('npm i')
c.run('npm run build')
+4 -29
View File
@@ -11,8 +11,6 @@ https://docs.djangoproject.com/en/1.8/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import raven
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -41,20 +39,18 @@ INSTALLED_APPS = (
'bootstrapform',
'django_extensions',
'corsheaders',
'raven.contrib.django.raven_compat',
)
LOCAL_APPS = ('apps.kassa',)
INSTALLED_APPS += LOCAL_APPS
MIDDLEWARE_CLASSES = (
MIDDLEWARE = (
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
@@ -75,6 +71,7 @@ TEMPLATES = [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'apps.kassa.context_processors.sentry',
],
},
},
@@ -124,11 +121,6 @@ TEKSTMELDING_ACTIVATION_SMS_TEMPLATE = 'Velkommen som medlem! Registrer deg her:
CORS_ORIGIN_ALLOW_ALL = True
RAVEN_CONFIG = {
'dsn': os.getenv('RAVEN_DSN'),
'release': raven.fetch_git_sha(BASE_DIR)
}
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
@@ -139,10 +131,6 @@ LOGGING = {
},
},
'handlers': {
'sentry': {
'level': 'WARNING', # To capture more than ERROR, change to WARNING, INFO, etc.
'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
@@ -152,31 +140,18 @@ LOGGING = {
'loggers': {
'root': {
'level': 'WARNING',
'handlers': ['sentry', 'console'],
},
'django': {
'level': 'DEBUG',
'handlers': ['console'],
'propagate': True,
},
'django.db.backends': {
'level': 'ERROR',
'handlers': ['console'],
'propagate': False,
},
'raven': {
'level': 'DEBUG',
'handlers': ['console'],
'propagate': False,
},
'sentry.errors': {
'level': 'DEBUG',
'handlers': ['console'],
'propagate': False,
},
},
}
SENTRY_DSN = None
SENTRY_ENVIRONMENT = 'dev'
# Local settings
try:
+6 -2
View File
@@ -1,9 +1,13 @@
from django.conf.urls import include, url
from django.contrib import admin
from django.urls import path
from django.contrib.auth import urls as auth_urls
from apps.kassa import urls as kassa_urls
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
path('admin/', admin.site.urls),
url('^', include(kassa_urls)),
url('^accounts/', include('django.contrib.auth.urls'))
path('accounts/', include(auth_urls))
]
+11 -10
View File
@@ -1,11 +1,12 @@
django-auth-ldap==1.7.0
django-auth-ldap==2.1.*
django-bootstrap-form==3.4
django-cors-headers==2.4.0
django-extensions==2.1.0
django-phonenumber-field==2.0.0
django==1.11.17
gunicorn==19.9.0
mysqlclient==1.3.13
phonenumbers==8.9.10
raven==6.9.0
requests==2.21.0
django-cors-headers==3.2.*
django-extensions==2.2.*
django-phonenumber-field==4.0.*
django==2.2.*
fabric==2.5.*
gunicorn==20.0.*
mysqlclient==1.4.6
phonenumbers==8.11.*
requests==2.22.*
sentry-sdk==0.13.*
+4
View File
@@ -0,0 +1,4 @@
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-transform-runtime"]
}
+29
View File
@@ -0,0 +1,29 @@
{
"env": {
"browser": true
},
"extends": [
"airbnb-base",
"plugin:prettier/recommended",
"plugin:import/errors",
"plugin:import/warnings"
],
"rules": {
"no-plusplus": "off",
"no-console": "off",
"no-underscore-dangle": "off",
"no-param-reassign": "off",
"no-restricted-syntax": "off",
},
"settings": {
"import/resolver": {
"node": {}, // https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-509384041
"webpack": {
"config": "webpack.config.js"
}
}
}
,
"plugins": ["prettier"],
"parser": "babel-eslint"
}
+5
View File
@@ -0,0 +1,5 @@
{
"singleQuote": true,
"printWidth": 120,
"arrowParens": "always"
}
File renamed without changes.
File renamed without changes.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

+652
View File
@@ -0,0 +1,652 @@
import * as $ from 'jquery';
import moment from 'moment';
import nunjucks from 'nunjucks';
import _ from 'lodash';
//
import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip';
import 'bootstrap-sass/assets/javascripts/bootstrap/popover';
import './templates';
import './index.scss';
/* Init and global vars */
let _dom;
let users;
let searching; /* search ajax request */
let selectedUser = null;
let membershipOrder = null; /* selected order object */
const cardForm = {
fields: {
cardNumber: false,
phoneNumber: false
}
};
const STATE_ICON = {
success: 'ok',
warning: 'warning-sign',
error: 'remove'
};
// TODO: replace with lodash template
const nunjucksEnv = new nunjucks.Environment();
const urls = {
userSearch: '/user-search/',
checkPhoneNumber: '/check-phone-number/',
checkCard: '/check-card/',
registerCardAndMembership: '/register-card-membership/',
renewMembership: '/renew-membership/'
};
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = $.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) {
return new URLSearchParams(window.location.search).get(name) || '';
}
function formatPhoneNumber(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 sortSearchResults(results) {
return _.sortBy(results, (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 formatResults(results) {
return nunjucksEnv.render('search_results.html', { results });
}
function renderSelectedUser(userData) {
let context;
if (!userData) {
context = { placeholder: true }; // dummy card
} else {
context = { res: userData, checked: true, no_user_actions: true };
}
const selectedUserHtml = nunjucksEnv.render('search_result.html', context);
_dom.selectedEntityWrap.html(selectedUserHtml);
}
function renderSelectedOrder(order) {
if (!order) {
return;
}
const context = { order, checked: true, no_user_actions: true };
const selectedOrderHtml = nunjucksEnv.render('selected_order.html', context);
_dom.selectedEntityWrap.html(selectedOrderHtml);
}
function setToast(message, messageType) {
const context = {
message,
message_type: messageType,
icon: STATE_ICON[messageType]
};
const html = nunjucksEnv.render('toast.html', context);
_dom.toastWrap.html(html);
_dom.toastWrap.addClass('visible');
setTimeout(() => {
_dom.toastWrap.removeClass('visible');
}, 5000);
}
function updateMemberShipButton(user, order) {
const todayPlusThirtyDays = 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 <= todayPlusThirtyDays && user.active_member_card) {
/* ...with a membership that expires soon can renew */
else if (user.last_membership.end_date <= todayPlusThirtyDays) {
_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);
} else if (order) {
/* Order */
/* If the membership has expired it can be renewed */
if (!order.product.is_valid && order.product.end_date <= todayPlusThirtyDays) {
_dom.membershipSubmitButton.prop('disabled', false);
}
} else {
_dom.membershipSubmitButton.prop('disabled', true);
}
}
function setSelectedUser(user, updateSearchResult) {
selectedUser = user;
updateMemberShipButton(user, null);
_dom.userIdField.val((user && user.id) || '').trigger('change');
if (updateSearchResult) {
const staleSearchResult = _dom.results.find('.search-result').removeClass('selected');
staleSearchResult.find('input').prop('checked', false);
/* User is still in search result, mark selected */
if (user && _.findWhere(users, { id: user.id })) {
const searchResult = _dom.results.find(`[data-user-id="${user.id}"]`).toggleClass('selected');
searchResult.find('input').prop('checked', true);
}
}
}
function setSelectedOrder(order) {
membershipOrder = order;
if (order) {
updateMemberShipButton(null, order);
}
_dom.orderUuidField.val((order && order.uuid) || '').trigger('change');
}
function setFieldState(fieldElement, state, helpText) {
if (!helpText) {
helpText = '';
}
const helpSpan = `<span class="help-block ">${helpText}</span>`;
const feedbackSpan = `<span class="glyphicon glyphicon-${STATE_ICON[state]} form-control-feedback" aria-hidden="true"></span>`;
const cssClass = `has-${state}`;
const formGroup = fieldElement.parent().parent();
formGroup.removeClass('has-success has-warning has-error has-feedback'); // reset
if (['success', 'warning', 'error'].indexOf(state) !== -1) {
formGroup.addClass(`${cssClass} has-feedback`);
/* Feedback icon */
const feedbackEl = formGroup.find('.form-control-feedback');
if (!feedbackEl.length) {
// add
formGroup.append(feedbackSpan);
} else {
// Update
feedbackEl.removeClass('glyphicon-ok glypicon-warning-sign glyphicon-remove');
feedbackEl.addClass(`glyphicon-${STATE_ICON[state]}`);
}
/* Help text */
const helpTextEl = formGroup.find('.help-block');
if (!helpTextEl.length) {
formGroup.append(helpSpan);
} else {
helpTextEl.text(helpText);
}
} else {
formGroup.find('.form-control-feedback').remove();
formGroup.find('.help-block').remove();
}
}
function resetCardForm(resetNative) {
if (resetNative) {
_dom.registerCardForm.get(0).reset();
}
/* Clear field states */
setFieldState(_dom.phoneNumberField, '');
cardForm.fields.phoneNumber = false;
setFieldState(_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(resetNative) {
if (resetNative) {
_dom.searchResetButton.get(0).reset();
}
/* Clear search results */
users = null;
_dom.results.html('');
}
function cardFormIsValid() {
return _.values(cardForm.fields).every((v) => v);
}
function updateSubmitButton() {
if (cardFormIsValid()) {
_dom.registerSubmitButton.prop('disabled', false);
return;
}
_dom.registerSubmitButton.prop('disabled', true);
}
function validatePhoneNumber(val) {
if (val.length === 0) {
cardForm.fields.phoneNumber = false;
updateSubmitButton();
return 'Phone number should not be empty.';
}
cardForm.fields.phoneNumber = true;
updateSubmitButton();
return '';
}
function validateCardNumber(val) {
if (val.length === 0) {
cardForm.fields.cardNumber = false;
updateSubmitButton();
return 'Card number should not be empty.';
}
if (val.length > 0 && val[0] !== '1') {
cardForm.fields.cardNumber = false;
updateSubmitButton();
return 'Card number should start with 1.';
}
if (val.length !== 9) {
cardForm.fields.cardNumber = false;
updateSubmitButton();
return 'Card number should be 9 digits long.';
}
cardForm.fields.cardNumber = true;
updateSubmitButton();
return '';
}
function getFormData(formElement) {
let formData = formElement.serializeArray();
formData = _.object(
_.map(formData, (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() {
const val = _dom.phoneNumberField.val().trim();
$.getJSON(urls.checkPhoneNumber, { phone_number: val }, (data) => {
/* Invalid phone number? */
if (data.error) {
setSelectedUser(null, true);
setSelectedOrder(null);
setFieldState(_dom.phoneNumberField, 'error', data.error);
cardForm.fields.phoneNumber = false;
updateSubmitButton();
return;
}
const { user } = data;
const { order } = data;
let 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}.`;
setFieldState(_dom.phoneNumberField, 'error', message);
cardForm.fields.phoneNumber = false;
setSelectedOrder(order);
updateSubmitButton();
return;
}
if (user) {
/* Existing user */
message = 'Phone number belongs to existing user.';
setFieldState(_dom.phoneNumberField, 'success', message);
setSelectedUser(user, true);
setSelectedOrder(null);
cardForm.fields.phoneNumber = true;
updateSubmitButton();
} else if (order) {
setSelectedOrder(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 */
setSelectedOrder(null);
setSelectedUser(null);
}
setFieldState(_dom.phoneNumberField, 'success', message);
cardForm.fields.phoneNumber = true;
updateSubmitButton();
});
}
/* Add filter |phoneNumber */
nunjucksEnv.addFilter('phoneNumber', formatPhoneNumber);
/* On document ready */
$(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')
};
const lazyCheckPhoneNumber = _.debounce(checkPhoneNumber, 250);
_dom.phoneNumberField.on('input', (e) => {
const val = e.target.value.trim();
const validationMsg = validatePhoneNumber(val);
if (validationMsg !== '') {
setSelectedUser(null, true);
setFieldState(_dom.phoneNumberField, 'error', validationMsg);
return;
}
lazyCheckPhoneNumber();
});
/* Card number as you type */
_dom.cardNumberField.on('input', (e) => {
/* Card number should exist in the database and not tied to existing user */
const val = e.target.value.trim();
const validationMsg = validateCardNumber(val);
if (validationMsg !== '') {
setFieldState(_dom.cardNumberField, 'error', validationMsg);
return;
}
$.getJSON(urls.checkCard, { card_number: val }, (data) => {
if (data.error) {
updateSubmitButton();
cardForm.fields.cardNumber = false;
setFieldState(_dom.cardNumberField, 'error', data.error);
return;
}
const card = data;
const { user } = data;
const { order } = data;
// const todayPlusThirtyDays = moment().add(30, 'days');
if (!card) {
cardForm.fields.cardNumber = false;
setFieldState(_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 */
setSelectedOrder(order);
cardForm.fields.cardNumber = true;
setFieldState(
_dom.cardNumberField,
'success',
`Card number is in use (not activated) and belongs to ${formatPhoneNumber(order.phone_number)}.`
);
} else if (card && card.registered !== null) {
let ownerString = '???';
if (user) {
ownerString = `existing user: ${user.first_name} ${user.last_name} (${formatPhoneNumber(user.phone_number)})`;
} else {
setSelectedOrder(order);
ownerString = `phone number: ${formatPhoneNumber(order.phone_number)}`;
}
cardForm.fields.cardNumber = false;
setFieldState(_dom.cardNumberField, 'error', `Card number is in use and belongs to ${ownerString}.`);
} else if (card && user === null) {
cardForm.fields.cardNumber = true;
setFieldState(_dom.cardNumberField, 'success');
} else {
cardForm.fields.cardNumber = true;
setFieldState(_dom.cardNumberField, '');
}
updateSubmitButton();
}).fail(() => {
cardForm.fields.cardNumber = false;
setFieldState(_dom.cardNumberField, 'error', 'Cannot find card number in database.');
});
});
/* User search as you type */
_dom.query.on('input', () => {
const val = _dom.query.val().trim();
if (val.length <= 2) {
_dom.results.html('');
return;
}
if (searching) {
searching.abort();
}
searching = $.getJSON(urls.userSearch, { search: val }, (data) => {
if (data.results && data.results.length > 0) {
const sortedResults = sortSearchResults(data.results);
_dom.results.html(formatResults(sortedResults));
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', () => {
const label = $(this).parent();
let number = label.attr('data-phone-number');
const userId = label.attr('data-user-id');
/* Update selected state */
$('.search-result').removeClass('selected');
label.toggleClass('selected');
setSelectedUser(_.findWhere(users, { id: userId }));
/* Update phone number field */
if (number === '-') {
number = '';
}
_dom.phoneNumberField.val(number).trigger('input');
});
/* On submit button click */
_dom.registerSubmitButton.on('click', (e) => {
e.preventDefault();
if (!cardFormIsValid()) {
setToast('Either phone number or card number is not valid', 'error');
return;
}
const 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(() => {
let successMessage = `New card registered ${payload.card_number}`;
if (payload.action === 'add_or_renew') {
const fullName = `${selectedUser.first_name} ${selectedUser.last_name}`;
successMessage = `New membership and card number ${payload.card_number} registered to ${fullName} `;
} else if (payload.action === 'new_card_membership') {
successMessage = `New membership and card registered to ${formatPhoneNumber(
payload.phone_number
)}. Activation SMS sent `;
} else if (payload.action === 'sms_card_notify') {
successMessage = `New card registered to ${formatPhoneNumber(
payload.phone_number
)}. Membership already paid ${membershipOrder.product.start_date}. Activation SMS sent `;
}
setToast(`${successMessage} :-)`, 'success');
resetCardForm(true);
})
.fail((data) => {
console.log('failed', data);
let errorText = data.responseText;
if (data.responseJSON) {
if (data.responseJSON.non_field_errors) {
errorText = data.responseJSON.non_field_errors.join('<br>');
} else {
errorText = 'Not sure why. :-(';
}
}
setToast(`Failed! ${errorText}`, 'error');
});
});
/* Membership renewal button */
_dom.membershipSubmitButton.on('click', (e) => {
e.preventDefault();
if (!selectedUser && !membershipOrder) {
setToast('Computer says no.', 'error');
return;
}
const 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((data) => {
let name = 'unknown';
if (data.user) {
name = `${selectedUser.first_name} ${selectedUser.last_name}`;
} else {
name = `phone number ${data.phone_number}`;
}
const successMessage = `Membership renewed for ${name} :-)`;
setToast(successMessage, 'success');
resetCardForm(true);
})
.fail((data) => {
console.log('failed', data);
let errorText = data.responseText;
if (data.responseJSON) {
if (data.responseJSON.non_field_errors) {
errorText = data.responseJSON.non_field_errors.join('<br>');
} else {
errorText = 'Not sure why. :-(';
}
}
setToast(`Failed! ${errorText}`, 'error');
});
});
/* On user id change */
_dom.userIdField.on('change', function() {
/* Render selected user in search form */
renderSelectedUser(selectedUser);
});
/* On order uuid change */
_dom.orderUuidField.on('change', function() {
/* Render selected order */
renderSelectedOrder(membershipOrder);
});
/* Reset buttons */
_dom.registerResetButton.on('click', function() {
resetCardForm();
});
_dom.searchResetButton.on('click', function() {
resetSearchForm();
});
/* Ninja add some icons to bootstrap form */
const 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 */
const selectedUserHtml = nunjucksEnv.render('search_result.html', { placeholder: true });
_dom.selectedEntityWrap.html(selectedUserHtml);
/* 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'
});
});
@@ -1,11 +1,11 @@
$icon-font-path: '../fonts/';
$icon-font-path: './fonts/';
$green-color: #5cb85c;
$red-color: #ff433d;
$yellow-color: #f8ff5b;
$active-color: darkred;
/* Vendor styles */
@import "../../bower_components/bootstrap-sass/assets/stylesheets/_bootstrap.scss";
@import "../node_modules/bootstrap-sass/assets/stylesheets/_bootstrap.scss";
/* Global styles */
body {
-635
View File
@@ -1,635 +0,0 @@
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'
});
});
@@ -1,7 +1,8 @@
/* eslint-disable */
(function() {(window.nunjucksPrecompiled = window.nunjucksPrecompiled || {})["search_result.html"] = (function() {
function root(env, context, frame, runtime, cb) {
var lineno = null;
var colno = null;
var lineno = 0;
var colno = 0;
var output = "";
try {
var parentTemplate = null;
@@ -42,7 +43,7 @@ output += "</span>\n ";
;
}
else {
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"last_membership") == runtime.contextOrFrameLookup(context, frame, "null")) {
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"last_membership") == null) {
output += "\n <span class=\"label label-default\">Registered</span>\n ";
;
}
@@ -107,18 +108,18 @@ root: root
})();
})();
(function() {(window.nunjucksPrecompiled = window.nunjucksPrecompiled || {})["search_results.html"] = (function() {
function root(env, context, frame, runtime, cb) {
var lineno = null;
var colno = null;
var lineno = 0;
var colno = 0;
var output = "";
try {
var parentTemplate = null;
output += "<div class=\"list-group\">\n ";
frame = frame.push();
var t_3 = runtime.contextOrFrameLookup(context, frame, "results");
if(t_3) {var t_2 = t_3.length;
if(t_3) {t_3 = runtime.fromIterator(t_3);
var t_2 = t_3.length;
for(var t_1=0; t_1 < t_3.length; t_1++) {
var t_4 = t_3[t_1];
frame.set("res", t_4);
@@ -133,15 +134,15 @@ output += "\n ";
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; }
env.getTemplate("search_result.html", false, "search_results.html", false, function(t_6,t_5) {
if(t_6) { cb(t_6); return; }
callback(null,t_5);});
});
tasks.push(
function(template, callback){
template.render(context.getVariables(), frame, function(t_8,t_6) {
template.render(context.getVariables(), frame, function(t_8,t_7) {
if(t_8) { cb(t_8); return; }
callback(null,t_6);});
callback(null,t_7);});
});
tasks.push(
function(result, callback){
@@ -171,11 +172,10 @@ root: root
})();
})();
(function() {(window.nunjucksPrecompiled = window.nunjucksPrecompiled || {})["selected_order.html"] = (function() {
function root(env, context, frame, runtime, cb) {
var lineno = null;
var colno = null;
var lineno = 0;
var colno = 0;
var output = "";
try {
var parentTemplate = null;
@@ -222,11 +222,10 @@ root: root
})();
})();
(function() {(window.nunjucksPrecompiled = window.nunjucksPrecompiled || {})["toast.html"] = (function() {
function root(env, context, frame, runtime, cb) {
var lineno = null;
var colno = null;
var lineno = 0;
var colno = 0;
var output = "";
try {
var parentTemplate = null;
@@ -253,3 +252,4 @@ root: root
})();
})();
+1
View File
@@ -0,0 +1 @@
export const identity = (v) => v;
-25
View File
@@ -1,25 +0,0 @@
{
"name": "kassa",
"private": true,
"dependencies": {
"jquery": "3.2.1",
"underscore": "1.8.3",
"list.js": "1.5.0",
"bootstrap-sass": "3.3.7",
"nunjucks": "~3.0.1",
"moment": "~2.18.1"
},
"devDependencies": {},
"resolutions": {
"jquery": "3.2.1"
},
"overrides": {
"bootstrap-sass": {
"main": [
"assets/stylesheets/_bootstrap.scss",
"assets/fonts/bootstrap/*",
"assets/javascripts/bootstrap.js"
]
}
}
}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

-635
View File
@@ -1,635 +0,0 @@
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 or non-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'
});
});
-20406
View File
File diff suppressed because it is too large. Load diff
-10
View File
@@ -1,10 +0,0 @@
@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
+10903
View File
File diff suppressed because it is too large. Load diff
+40 -25
View File
@@ -1,35 +1,50 @@
{
"name": "kassa",
"version": "0.0.1",
"private": true,
"scripts": {
"build": "webpack -p --env production --display-error-details --mode production",
"build:templates": "echo '/* eslint-disable */' > app/templates.js && nunjucks-precompile app/templates >> app/templates.js",
"start": "webpack-dev-server --hot --env development --mode development",
"format": "eslint --fix \"app/**/*.js\"",
"lint": "eslint --quiet \"app/**/*.js\""
},
"dependencies": {
"jshint": "^2.9.5"
"bootstrap-sass": "^3.4.1",
"jquery": "^3.4.1",
"list.js": "^1.5.0",
"lodash": "^4.17.15",
"moment": "^2.24.0",
"nunjucks": "^3.2.0"
},
"devDependencies": {
"browser-sync": "^2.7.6",
"del": "^3.0.0",
"gulp": "^3.8.11",
"gulp-autoprefixer": "^4.0.0",
"gulp-concat": "~2.6.1",
"gulp-csso": "^3.0.0",
"gulp-filter": "^5.0.0",
"gulp-flatten": "^0.3.1",
"gulp-imagemin": "^3.3.0",
"gulp-jshint": "^2.0.4",
"gulp-load-plugins": "^1.5.0",
"gulp-nunjucks": "^3.0.0",
"gulp-plumber": "~1.1.0",
"gulp-rename": "~1.2.2",
"gulp-sass": "~3.1.0",
"gulp-size": "^2.1.0",
"gulp-uglify": "^3.0.0",
"gulp-useref": "^3.1.2",
"gulp-util": "~3.0.4",
"jshint-stylish": "^2.0.0",
"main-bower-files": "^2.8.0",
"opn": "^5.1.0"
"@babel/core": "^7.7.7",
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.7",
"@babel/preset-react": "^7.7.4",
"babel-eslint": "^10.0.3",
"babel-loader": "^8.0.6",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.4.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^6.7.0",
"eslint-import-resolver-babel-module": "^5.1.0",
"eslint-plugin-import": "^2.19.1",
"eslint-plugin-prettier": "^3.1.2",
"file-loader": "^5.0.2",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.13.0",
"prettier": "^1.19.1",
"sass-loader": "^8.0.0",
"style-loader": "^1.1.2",
"url-loader": "^3.0.0",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1"
},
"engines": {
"node": ">=0.10.0"
"node": "12"
}
}
+96
View File
@@ -0,0 +1,96 @@
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const baseStyleLoader = (env) => (env === 'production' ? MiniCssExtractPlugin.loader : require.resolve('style-loader'));
module.exports = (env, argv) => {
return {
entry: './app/index.js',
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
resolve: {
extensions: ['.jsx', '.js', '.json', '.scss', '.css'],
modules: ['node_modules']
},
plugins: [
new MiniCssExtractPlugin(),
env === 'production' ? new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/) : null,
env !== 'production' ? new HtmlWebpackPlugin({ template: './app/index.html' }) : null,
new webpack.ProvidePlugin({
jQuery: 'jquery'
})
].filter(Boolean),
module: {
rules: [
{
test: /\.(png|jpg|gif|svg)$/,
use: [
{
loader: require.resolve('file-loader'),
options: {}
}
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: require.resolve('file-loader')
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: require.resolve('babel-loader')
},
{
test: /\.css$/,
loaders: [baseStyleLoader(env), require.resolve('css-loader')]
},
{
test: /\.scss$/,
loaders: [
baseStyleLoader(env),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: false,
sourceMap: true
}
},
{
loader: require.resolve('sass-loader'),
options: {
sassOptions: {
precision: 8
}
}
}
]
}
]
},
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
historyApiFallback: true,
overlay: {
errors: true,
warnings: false
},
port: 8001,
publicPath: '/',
proxy: {
'/api': 'http://localhost:8000'
// TODO: get urls
},
stats: {
assets: false,
modules: false,
chunks: false
}
},
devtool: env === 'production' ? 'source-map' : 'cheap-module-eval-source-map'
};
};
-4546
View File
File diff suppressed because it is too large. Load diff