Merge pull request #491 from tiwariav/task/3786/hosting_invoice_page

Task/3786 order-details/invoice page redesign
This commit is contained in:
Arvind Tiwari 2017-09-23 20:07:05 +05:30 committed by GitHub
commit 2385c108e1
13 changed files with 790 additions and 230 deletions

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-09-20 20:23+0000\n"
"POT-Creation-Date: 2017-09-23 19:00+0530\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -321,10 +321,14 @@ msgstr "Als gelesen markieren"
msgid "All notifications"
msgstr "Alle Benachrichtigungen"
msgid "Date"
msgstr "Datum"
#, python-format
msgid "%(page_header_text)s"
msgstr ""
msgid "Status:"
msgid "Invoice Date"
msgstr "Rechnung Datum"
msgid "Status"
msgstr ""
msgid "Approved"
@ -333,10 +337,10 @@ msgstr "Akzeptiert"
msgid "Declined"
msgstr "Abgelehnt"
msgid "Billed To:"
msgid "Billed to"
msgstr "Rechnungsadresse"
msgid "Payment Method:"
msgid "Payment method"
msgstr "Bezahlmethode"
msgid "ending in"
@ -345,6 +349,9 @@ msgstr "endend in"
msgid "Order summary"
msgstr "Bestellungsübersicht"
msgid "Product"
msgstr ""
msgid "Cores"
msgstr "Prozessorkerne"
@ -354,15 +361,9 @@ msgstr "Arbeitsspeicher"
msgid "Disk space"
msgstr "Festplattenkapazität"
msgid "Configuration"
msgstr "Konfiguration"
msgid "Total"
msgstr "Gesamt"
msgid "Month"
msgstr "Monat"
#, python-format
msgid ""
"By clicking \"Place order\" this plan will charge your credit card account "
@ -372,8 +373,10 @@ msgstr ""
"pro Monat belastet"
msgid "Place order"
msgstr "Bestelle"
msgstr "Bestellen"
msgid "BACK TO LIST"
msgstr "ZURÜCK ZUR LISTE"
msgid "Processing..."
msgstr "Abarbeitung..."
@ -387,14 +390,14 @@ msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
msgid "Order Nr."
msgstr "Bestellung Nr."
msgid "Date"
msgstr "Datum"
msgid "Amount"
msgstr "Betrag"
msgid "Status"
msgstr ""
msgid "See Invoice"
msgstr "Rechnung"
msgstr "Siehe Rechnung"
msgid "Page"
msgstr ""
@ -405,9 +408,15 @@ msgstr ""
msgid "Your Order"
msgstr "Deine Bestellung"
msgid "Configuration"
msgstr "Konfiguration"
msgid "including VAT"
msgstr "inkl. Mehrwertsteuer"
msgid "Month"
msgstr "Monat"
msgid "Billing Address"
msgstr "Rechnungsadresse"
@ -573,9 +582,6 @@ msgstr "Wir sind hier, um Dir zu helfen!"
msgid "CONTACT"
msgstr "KONTACT"
msgid "BACK TO LIST"
msgstr "ZURÜCK ZUR LISTE"
msgid "Terminate your Virtual Machine"
msgstr "Deine Virtuelle Maschine beenden"
@ -639,6 +645,15 @@ msgstr "Ungültige Kreditkarte"
msgid "Confirm Order"
msgstr "Bestellung Bestätigen"
msgid ""
"The VM you are looking for is unavailable at the moment. Please contact Data "
"Center Light support."
msgstr "Kontaktiere den Data Center Light Support."
msgid "In order to create a VM, you need to create/upload your SSH KEY first."
msgstr ""
"Um eine VM zu erstellen musst du zuerst einen SSH-Key erstellen / hochladen."
msgid "Thank you for the order."
msgstr "Danke für Deine Bestellung."
@ -646,12 +661,8 @@ msgid ""
"Your VM will be up and running in a few moments. We will send you a "
"confirmation email as soon as it is ready."
msgstr ""
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, "
"sobald Du auf sie zugreifen kannst."
msgid "In order to create a VM, you need to create/upload your SSH KEY first."
msgstr ""
"Um eine VM zu erstellen musst du zuerst einen SSH-Key erstellen / hochladen."
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du "
"auf sie zugreifen kannst."
msgid ""
"We could not find the requested VM. Please "

View file

@ -356,3 +356,19 @@
.no-cards a {
color: #7ca3d0;
}
.btn-plain {
background: transparent;
border: none;
fill: #595959;
color: #595959;
outline: none;
}
.btn-plain:hover,
.btn-plain:focus,
.btn-plain:active,
.btn-plain:active:focus {
outline: none;
color: #999;
fill: #999;
}

View file

@ -580,9 +580,9 @@ a.unlink:hover {
}
.dcl-place-order-text{
font-size: 13px;
/* font-size: 13px; */
color: #808080;
margin-bottom: 15px;
/* margin-bottom: 15px; */
}
.dcl-order-table-total .tbl-total {

View file

@ -1,4 +1,13 @@
.order-detail-container {padding-top: 70px; padding-bottom: 70px; margin-bottom: 70px;}
.order-detail-container {
max-width: 600px;
margin: 100px auto 40px;
border: 1px solid #ccc;
padding: 15px;
}
@media(min-width: 768px) {
padding: 30px;
}
.order-detail-container .invoice-title h2, .invoice-title h3 {
display: inline-block;
@ -15,3 +24,67 @@
.order-detail-container .table > tbody > tr > .thick-line {
border-top: 2px solid;
}
.order-detail-container .dashboard-title-thin {
margin-top: 0;
margin-left: -3px;
}
.order-detail-container .dashboard-title-thin .un-icon {
margin-top: -6px;
}
.order-detail-container .dashboard-container-head {
position: relative;
padding: 0;
margin-bottom: 38px;
}
.order-detail-container .dashboard-container-options {
position: absolute;
top: 10px;
right: 0;
}
.order-detail-container .dashboard-container-options .svg-img {
height: 22px;
width: 22px;
}
.order-detail-container .order-details {
margin-bottom: 30px;
}
.order-detail-container .order-details strong {
color: #595959;
}
.order-detail-container h4 {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
}
.order-detail-container p {
margin-bottom: 5px;
color: #595959;
}
.order-detail-container hr {
margin: 15px 0;
}
@media (max-width: 767px) {
.order-confirm-btn {
text-align: center;
margin-top: 10px;
}
.order-detail-container .dashboard-container-options {
position: absolute;
top: 4px;
right: -4px;
}
.order-detail-container .dashboard-container-options .svg-img {
height: 16px;
width: 16px;
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="492px" height="649px" viewBox="0 0 492 649" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>icon-pdf</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill-rule="evenodd">
<g id="icon-pdf" fill-rule="nonzero">
<g id="Group" transform="translate(93.000000, 269.000000)">
<path d="M98,47.8 C98,54.8 97,61.7 95.1,66.6 C93.1,72.5 90.1,76.5 86.2,80.5 C82.2,84.5 76.3,87.4 70.4,89.4 C64.5,91.4 55.6,92.3 46.6,92.3 L34.7,92.3 L34.7,134 L0,134 L0,0.3 L45.5,0.3 C54.4,0.3 62.4,1.3 69.3,3.2 C76.2,5.2 81.2,8.2 85.1,12.1 C89.1,16.1 92.1,21 94,27 C96.9,32 98,38.9 98,47.8 Z M61.3,46.8 C61.3,42.8 61.3,39.9 60.3,37.9 C60.3,35.9 59.3,33.9 57.4,32 C56.4,31 54.5,30 52.4,29.1 C50.4,29.1 47.4,28.1 44.5,28.1 L34.6,28.1 L34.6,63.7 L45.5,63.7 C48.4,63.7 50.4,63.7 52.5,62.7 C54.5,61.7 56.5,61.7 57.5,59.8 C58.5,57.8 59.5,56.9 60.4,54.8 C61.3,53.8 61.3,50.8 61.3,46.8 Z" id="Shape"></path>
<path d="M214.8,66.6 C214.8,78.5 213.8,89.3 210.8,97.3 C208.8,106.2 204.9,113.1 199.9,118.1 C195,123.1 189,127 182.1,130 C175.2,132 167.3,134 157.3,134 L108.8,134 L108.8,0.3 L156.3,0.3 C166.2,0.3 175.1,1.3 182,4.3 C188.9,7.2 194.9,10.2 199.8,16.2 C204.8,21.2 207.7,28.1 209.7,37 C213.8,44.8 214.8,54.7 214.8,66.6 Z M178.1,65.6 C178.1,58.7 178.1,52.7 177.1,47.8 C176.1,42.8 175.1,39.9 174.2,36.9 C172.2,34 170.2,32.9 168.3,31.9 C165.4,30.9 162.4,30.9 158.4,30.9 L145.6,30.9 L145.6,103.2 L158.4,103.2 C162.4,103.2 165.4,103.2 168.3,102.2 C171.2,101.2 173.3,99.3 174.2,97.2 C176.2,94.3 177.1,91.3 178.2,86.3 C178.1,80.5 178.1,74.5 178.1,65.6 Z" id="Shape"></path>
<polygon id="Shape" points="264.3 32 264.3 55.8 304.9 55.8 304.9 85.5 264.3 85.5 264.3 135 229.7 135 229.7 0.3 312.9 0.3 313.9 31 264.4 31 264.4 32"></polygon>
</g>
<path d="M491.3,649 L0.7,649 L0.7,0 L346.1,0 L491.3,145.2 L491.3,649 L491.3,649 Z M44.7,605 L448.4,605 L448.4,162.8 L327.4,42.9 L44.7,42.9 L44.7,605 Z" id="Shape"></path>
<polygon id="Shape" points="469.3 176 315.3 176 315.3 20.9 359.3 20.9 359.3 132 469.3 132"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="612px" height="612px" viewBox="0 0 612 612" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>54471</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill-rule="evenodd">
<g id="54471" fill-rule="nonzero">
<path d="M0,225.633 L0,386.367 C0,417.272 30.6,417.272 30.6,417.272 L83.454,417.272 L83.454,389.454 L27.818,389.454 L27.818,222.545 L584.181,222.545 L584.181,389.454 L528.545,389.454 L528.545,417.272 L581.4,417.272 C581.4,417.272 612,417.272 612,386.367 L612,225.633 C612,225.633 612,194.727 581.4,194.727 L30.6,194.727 C0,194.728 0,225.633 0,225.633 Z" id="Shape"></path>
<polygon id="Shape" points="500.728 166.909 500.728 0 111.273 0 111.273 166.909 139.091 166.909 139.091 27.818 472.909 27.818 472.909 166.909"></polygon>
<path d="M528.546,292.091 C528.546,278.182 514.358,278.182 514.358,278.182 L97.642,278.182 C97.642,278.182 83.455,278.182 83.455,292.091 C83.455,306 97.642,306 97.642,306 L514.359,306 C514.358,306 528.546,306 528.546,292.091 Z" id="Shape"></path>
<rect id="Rectangle-path" x="166.909" y="500.728" width="278.182" height="27.818"></rect>
<path d="M500.728,612 L500.728,389.454 L111.273,389.454 L111.273,612 L500.728,612 Z M139.091,417.272 L472.909,417.272 L472.909,584.181 L139.091,584.181 L139.091,417.272 Z" id="Shape"></path>
<rect id="Rectangle-path" x="166.909" y="445.091" width="278.182" height="27.818"></rect>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,387 @@
/**
* @license
*
* MIT License
*
* Copyright (c) 2017 Erik Koopmans
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Generate a PDF from an HTML element or string using html2canvas and jsPDF.
*
* @param {Element|string} source The source element or HTML string.
* @param {Object=} opt An object of optional settings: 'margin', 'filename',
* 'image' ('type' and 'quality'), and 'html2canvas' / 'jspdf', which are
* sent as settings to their corresponding functions.
*/
var html2pdf = (function(html2canvas, jsPDF) {
/* ---------- MAIN FUNCTION ---------- */
var html2pdf = function(source, opt) {
// Handle input.
opt = objType(opt) === 'object' ? opt : {};
var source = html2pdf.parseInput(source, opt);
// Determine the PDF page size.
var pageSize = jsPDF.getPageSize(opt.jsPDF);
pageSize.inner = {
width: pageSize.width - opt.margin[1] - opt.margin[3],
height: pageSize.height - opt.margin[0] - opt.margin[2]
};
pageSize.inner.ratio = pageSize.inner.height / pageSize.inner.width;
// Copy the source element into a PDF-styled container div.
var container = html2pdf.makeContainer(source, pageSize);
var overlay = container.parentElement;
// Get the locations of all hyperlinks.
if (opt.enableLinks) {
// Find all anchor tags and get the container's bounds for reference.
opt.links = [];
var links = container.querySelectorAll('a');
var containerRect = unitConvert(container.getBoundingClientRect(), pageSize.k);
// Treat each client rect as a separate link (for text-wrapping).
Array.prototype.forEach.call(links, function(link) {
var clientRects = link.getClientRects();
for (var i=0; i<clientRects.length; i++) {
var clientRect = unitConvert(clientRects[i], pageSize.k);
clientRect.left -= containerRect.left;
clientRect.top -= containerRect.top;
opt.links.push({ el: link, clientRect: clientRect });
}
});
}
// Render the canvas and pass the result to makePDF.
var onRendered = opt.html2canvas.onrendered || function() {};
opt.html2canvas.onrendered = function(canvas) {
onRendered(canvas);
document.body.removeChild(overlay);
html2pdf.makePDF(canvas, pageSize, opt);
}
html2canvas(container, opt.html2canvas);
};
html2pdf.parseInput = function(source, opt) {
// Parse the opt object.
opt.jsPDF = opt.jsPDF || {};
opt.html2canvas = opt.html2canvas || {};
opt.filename = opt.filename && objType(opt.filename) === 'string' ? opt.filename : 'file.pdf';
opt.enableLinks = opt.hasOwnProperty('enableLinks') ? opt.enableLinks : true;
opt.image = opt.image || {};
opt.image.type = opt.image.type || 'jpeg';
opt.image.quality = opt.image.quality || 0.95;
// Parse the margin property of the opt object.
switch (objType(opt.margin)) {
case 'undefined':
opt.margin = 0;
case 'number':
opt.margin = [opt.margin, opt.margin, opt.margin, opt.margin];
break;
case 'array':
if (opt.margin.length === 2) {
opt.margin = [opt.margin[0], opt.margin[1], opt.margin[0], opt.margin[1]];
}
if (opt.margin.length === 4) {
break;
}
default:
throw 'Invalid margin array.';
}
// Parse the source element/string.
if (!source) {
throw 'Missing source element or string.';
} else if (objType(source) === 'string') {
source = createElement('div', { innerHTML: source });
} else if (objType(source) === 'element') {
source = cloneNode(source, opt.html2canvas.javascriptEnabled);
} else {
throw 'Invalid source - please specify an HTML Element or string.';
}
// Return the parsed input (opt is modified in-place, no need to return).
return source;
};
html2pdf.makeContainer = function(source, pageSize) {
// Define the CSS styles for the container and its overlay parent.
var overlayCSS = {
position: 'fixed', overflow: 'hidden', zIndex: 1000,
left: 0, right: 0, bottom: 0, top: 0,
backgroundColor: 'rgba(0,0,0,0.8)'
};
var containerCSS = {
position: 'absolute', width: pageSize.inner.width + pageSize.unit,
left: 0, right: 0, top: 0, height: 'auto', margin: 'auto',
backgroundColor: 'white'
};
// Set the overlay to hidden (could be changed in the future to provide a print preview).
overlayCSS.opacity = 0;
// Create and attach the elements.
var overlay = createElement('div', { className: 'html2pdf__overlay', style: overlayCSS });
var container = createElement('div', { className: 'html2pdf__container', style: containerCSS });
container.appendChild(source);
overlay.appendChild(container);
document.body.appendChild(overlay);
// Enable page-breaks.
var pageBreaks = source.querySelectorAll('.html2pdf__page-break');
var pxPageHeight = pageSize.inner.height * pageSize.k / 72 * 96;
Array.prototype.forEach.call(pageBreaks, function(el) {
el.style.display = 'block';
var clientRect = el.getBoundingClientRect();
el.style.height = pxPageHeight - (clientRect.top % pxPageHeight) + 'px';
}, this);
// Return the container.
return container;
};
html2pdf.makePDF = function(canvas, pageSize, opt) {
// Calculate the number of pages.
var ctx = canvas.getContext('2d');
var pxFullHeight = canvas.height;
var pxPageHeight = Math.floor(canvas.width * pageSize.inner.ratio);
var nPages = Math.ceil(pxFullHeight / pxPageHeight);
// Create a one-page canvas to split up the full image.
var pageCanvas = document.createElement('canvas');
var pageCtx = pageCanvas.getContext('2d');
var pageHeight = pageSize.inner.height;
pageCanvas.width = canvas.width;
pageCanvas.height = pxPageHeight;
// Initialize the PDF.
var pdf = new jsPDF(opt.jsPDF);
for (var page=0; page<nPages; page++) {
// Trim the final page to reduce file size.
if (page === nPages-1) {
pageCanvas.height = pxFullHeight % pxPageHeight;
pageHeight = pageCanvas.height * pageSize.inner.width / pageCanvas.width;
}
// Display the page.
var w = pageCanvas.width;
var h = pageCanvas.height;
pageCtx.fillStyle = 'white';
pageCtx.fillRect(0, 0, w, h);
pageCtx.drawImage(canvas, 0, page*pxPageHeight, w, h, 0, 0, w, h);
// Add the page to the PDF.
if (page) pdf.addPage();
var imgData = pageCanvas.toDataURL('image/' + opt.image.type, opt.image.quality);
pdf.addImage(imgData, opt.image.type, opt.margin[1], opt.margin[0],
pageSize.inner.width, pageHeight);
// Add hyperlinks.
if (opt.enableLinks) {
var pageTop = page * pageSize.inner.height;
opt.links.forEach(function(link) {
if (link.clientRect.top > pageTop && link.clientRect.top < pageTop + pageSize.inner.height) {
var left = opt.margin[1] + link.clientRect.left;
var top = opt.margin[0] + link.clientRect.top - pageTop;
pdf.link(left, top, link.clientRect.width, link.clientRect.height, { url: link.el.href });
}
});
}
}
// Finish the PDF.
pdf.save( opt.filename );
}
/* ---------- UTILS ---------- */
// Determine the type of a variable/object.
var objType = function(obj) {
if (typeof obj === 'undefined') return 'undefined';
else if (typeof obj === 'string' || obj instanceof String) return 'string';
else if (typeof obj === 'number' || obj instanceof Number) return 'number';
else if (!!obj && obj.constructor === Array) return 'array';
else if (obj && obj.nodeType === 1) return 'element';
else if (typeof obj === 'object') return 'object';
else return 'unknown';
};
// Create an HTML element with optional className, innerHTML, and style.
var createElement = function(tagName, opt) {
var el = document.createElement(tagName);
if (opt.className) el.className = opt.className;
if (opt.innerHTML) {
el.innerHTML = opt.innerHTML;
var scripts = el.getElementsByTagName('script');
for (var i = scripts.length; i-- > 0; null) {
scripts[i].parentNode.removeChild(scripts[i]);
}
}
for (var key in opt.style) {
el.style[key] = opt.style[key];
}
return el;
};
// Deep-clone a node and preserve contents/properties.
var cloneNode = function(node, javascriptEnabled) {
// Recursively clone the node.
var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false);
for (var child = node.firstChild; child; child = child.nextSibling) {
if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') {
clone.appendChild(cloneNode(child, javascriptEnabled));
}
}
if (node.nodeType === 1) {
// Preserve contents/properties of special nodes.
if (node.nodeName === 'CANVAS') {
clone.width = node.width;
clone.height = node.height;
clone.getContext('2d').drawImage(node, 0, 0);
} else if (node.nodeName === 'TEXTAREA' || node.nodeName === 'SELECT') {
clone.value = node.value;
}
// Preserve the node's scroll position when it loads.
clone.addEventListener('load', function() {
clone.scrollTop = node.scrollTop;
clone.scrollLeft = node.scrollLeft;
}, true);
}
// Return the cloned node.
return clone;
}
// Convert units using the conversion value 'k' from jsPDF.
var unitConvert = function(obj, k) {
var newObj = {};
for (var key in obj) {
newObj[key] = obj[key] * 72 / 96 / k;
}
return newObj;
};
// Get dimensions of a PDF page, as determined by jsPDF.
jsPDF.getPageSize = function(orientation, unit, format) {
// Decode options object
if (typeof orientation === 'object') {
var options = orientation;
orientation = options.orientation;
unit = options.unit || unit;
format = options.format || format;
}
// Default options
unit = unit || 'mm';
format = format || 'a4';
orientation = ('' + (orientation || 'P')).toLowerCase();
var format_as_string = ('' + format).toLowerCase();
// Size in pt of various paper formats
pageFormats = {
'a0' : [2383.94, 3370.39], 'a1' : [1683.78, 2383.94],
'a2' : [1190.55, 1683.78], 'a3' : [ 841.89, 1190.55],
'a4' : [ 595.28, 841.89], 'a5' : [ 419.53, 595.28],
'a6' : [ 297.64, 419.53], 'a7' : [ 209.76, 297.64],
'a8' : [ 147.40, 209.76], 'a9' : [ 104.88, 147.40],
'a10' : [ 73.70, 104.88], 'b0' : [2834.65, 4008.19],
'b1' : [2004.09, 2834.65], 'b2' : [1417.32, 2004.09],
'b3' : [1000.63, 1417.32], 'b4' : [ 708.66, 1000.63],
'b5' : [ 498.90, 708.66], 'b6' : [ 354.33, 498.90],
'b7' : [ 249.45, 354.33], 'b8' : [ 175.75, 249.45],
'b9' : [ 124.72, 175.75], 'b10' : [ 87.87, 124.72],
'c0' : [2599.37, 3676.54], 'c1' : [1836.85, 2599.37],
'c2' : [1298.27, 1836.85], 'c3' : [ 918.43, 1298.27],
'c4' : [ 649.13, 918.43], 'c5' : [ 459.21, 649.13],
'c6' : [ 323.15, 459.21], 'c7' : [ 229.61, 323.15],
'c8' : [ 161.57, 229.61], 'c9' : [ 113.39, 161.57],
'c10' : [ 79.37, 113.39], 'dl' : [ 311.81, 623.62],
'letter' : [612, 792],
'government-letter' : [576, 756],
'legal' : [612, 1008],
'junior-legal' : [576, 360],
'ledger' : [1224, 792],
'tabloid' : [792, 1224],
'credit-card' : [153, 243]
};
// Unit conversion
switch (unit) {
case 'pt': k = 1; break;
case 'mm': k = 72 / 25.4; break;
case 'cm': k = 72 / 2.54; break;
case 'in': k = 72; break;
case 'px': k = 72 / 96; break;
case 'pc': k = 12; break;
case 'em': k = 12; break;
case 'ex': k = 6; break;
default:
throw ('Invalid unit: ' + unit);
}
// Dimensions are stored as user units and converted to points on output
if (pageFormats.hasOwnProperty(format_as_string)) {
pageHeight = pageFormats[format_as_string][1] / k;
pageWidth = pageFormats[format_as_string][0] / k;
} else {
try {
pageHeight = format[1];
pageWidth = format[0];
} catch (err) {
throw new Error('Invalid format: ' + format);
}
}
// Handle page orientation
if (orientation === 'p' || orientation === 'portrait') {
orientation = 'p';
if (pageWidth > pageHeight) {
tmp = pageWidth;
pageWidth = pageHeight;
pageHeight = tmp;
}
} else if (orientation === 'l' || orientation === 'landscape') {
orientation = 'l';
if (pageHeight > pageWidth) {
tmp = pageWidth;
pageWidth = pageHeight;
pageHeight = tmp;
}
} else {
throw('Invalid orientation: ' + orientation);
}
// Return information (k is the unit conversion ratio from pts)
var info = { 'width': pageWidth, 'height': pageHeight, 'unit': unit, 'k': k };
return info;
};
// Expose the html2pdf function.
return html2pdf;
}(html2canvas, jsPDF));

View file

@ -0,0 +1,15 @@
$(document).ready(function() {
$('.btn-pdf').click(function(e) {
e.preventDefault();
var $target = $($(this).attr('data-target')) || $('body');
var fileName = $target.attr('id') + '.pdf';
html2pdf($target[0], {
filename: fileName,
});
});
$('.btn-print').click(function(e) {
e.preventDefault();
console.log('a');
window.print();
});
});

View file

@ -1,4 +1,5 @@
$(document).ready(function () {
$('.modal-text').removeClass('hide');
var create_vm_form = $('#virtual_machine_create_form');
create_vm_form.submit(function () {
$('#btn-create-vm').prop('disabled', true);
@ -22,6 +23,7 @@ $(document).ready(function () {
fa_icon = $('.modal-icon > .fa');
fa_icon.attr('class', 'fa fa-times');
$('.modal-header > .close').attr('class', 'close');
$('.modal-text').addClass('hide');
if (typeof(create_vm_error_message) !== 'undefined') {
$('#createvm-modal-title').text(create_vm_error_message);
}

View file

@ -94,6 +94,13 @@
<!-- Init JavaScript -->
<script src="{% static 'hosting/js/initial.js' %}"></script>
{% block js_extra %}
{% comment %}
this block is above some files, because on stripe error scripts below the stripe
script are not properly executed.
{% endcomment %}
{% endblock js_extra %}
<script src="https://js.stripe.com/v3/"></script>
<script src="https://js.stripe.com/v2/"></script>
<!-- Stripe Lib -->
@ -108,8 +115,6 @@
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment-with-locales.js"></script>
</body>
</html>

View file

@ -4,206 +4,190 @@
{% load custom_tags %}
{% block content %}
<div class="order-detail-container">
{{order.pk}}
<div id="order-detail{{order.pk}}" class="order-detail-container">
{% if messages %}
<div class="row">
<div class="col-xs-12 col-md-8 col-md-offset-2">
<br/>
<div class="alert alert-warning">
{% for message in messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% if not error %}
<div class="row">
<div class="col-xs-12 col-md-8 col-md-offset-2">
<div class="invoice-title">
<h2>{{page_header_text}}</h2>
<h3 class="pull-right">
<div class="dashboard-container-head">
<h1 class="dashboard-title-thin">
<img src="{% static 'hosting/img/billing.svg' %}" class="un-icon">{% blocktrans with page_header_text=page_header_text|default:"Invoice" %}{{page_header_text}}{% endblocktrans %}
</h1>
<div class="dashboard-container-options">
<button type="button" class="btn-plain btn-pdf" data-target="#order-detail{{order.pk}}"><img src="{% static 'hosting/img/icon-pdf.svg' %}" class="svg-img"></button>
<button type="button" class="btn-plain btn-print"><img src="{% static 'hosting/img/icon-print.svg' %}" class="svg-img"></button>
</div>
</div>
<div class="order-details">
{% if order %}
<p>
<strong>{% trans "Order #" %} {{order.id}}</strong>
</p>
{% endif %}
<p>
<strong>{% trans "Invoice Date" %}:</strong>
<span id="order-created_at">
{% if order %}
{% trans "Order #"%} {{order.id}}
{{order.created_at|date:'Y-m-d H:i'}}
{% else %}
{% now "Y-m-d H:i" %}
{% endif %}
</h3>
</span>
</p>
{% if order %}
<p>
<strong>{% trans "Status" %}: </strong>
<strong>
{% if order.status == 'Approved' %}
<span class="vm-color-online">{% trans "Approved" %}</span>
{% else %}
<span class="vm-status-failed">{% trans "Declined" %}</span>
{% endif %}
</strong>
</p>
{% endif %}
<hr>
<div>
<address>
<h4>{% trans "Billed to" %}:</h4>
<p>
{% if order %}
{{user.name}}<br>
{{order.billing_address.street_address}}, {{order.billing_address.postal_code}}<br>
{{order.billing_address.city}}, {{order.billing_address.country}}
{% else %}
{% with request.session.billing_address_data as billing_address %}
{{billing_address.cardholder_name}}<br>
{{billing_address.street_address}}, {{billing_address.postal_code}}<br>
{{billing_address.city}}, {{billing_address.country}}
{% endwith %}
{% endif %}
</p>
</address>
</div>
<hr>
<div class="row">
<div class="col-xs-12 col-md-6 pull-right order-confirm-date">
<address>
<strong>{% trans "Date"%}:</strong><br>
<span id="order-created_at">
{% if order %}
{{order.created_at|date:'Y-m-d H:i'}}
{% else %}
{% now "Y-m-d H:i" %}
{% endif %}
</span><br><br>
{% if order %}
<strong>{% trans "Status:"%}</strong><br>
{% if order.status == 'Approved' %}
<strong class="text-success">
{% trans "Approved" %}
</strong>
{% else %}
<strong class="text-danger">
{% trans "Declined" %}
</strong>
{% endif %}
<br><br>
{% endif %}
</address>
</div>
<div class="col-xs-12 col-md-6">
<address>
<h3><b>{% trans "Billed To:"%}</b></h3>
{% if order %}
{{user.name}}<br>
{{order.billing_address.street_address}},{{order.billing_address.postal_code}}<br>
{{order.billing_address.city}},
{{order.billing_address.country}}.
{% else %}
{% with request.session.billing_address_data as billing_address %}
{{billing_address|get_value_from_dict:'cardholder_name'}}<br>
{{billing_address|get_value_from_dict:'street_address'}},
{{billing_address|get_value_from_dict:'postal_code'}}<br>
{{billing_address|get_value_from_dict:'city'}},
{{billing_address|get_value_from_dict:'country'}}.
{% endwith %}
{% endif %}
</address>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<address>
<strong>{% trans "Payment Method:"%}</strong><br>
{% if order %}
<div>
<h4>{% trans "Payment method" %}:</h4>
<p>
{% if order %}
{{order.cc_brand}} {% trans "ending in" %} ****
{{order.last4}}<br>
{{user.email}}
{% else %}
{{cc_brand}} {% trans "ending in" %} ****
{% else %}
{{cc_brand|default:'Card'}} {% trans "ending in" %} ****
{{cc_last4}}<br>
{{request.session.user.email}}
{% if request.user.is_authenticated %}
{{request.user.email}}
{% else %}
{{request.session.user.email}}
{% endif %}
</address>
{% endif %}
</p>
</div>
<hr>
<div>
<h4>{% trans "Order summary" %}</h4>
<p>
<strong>{% trans "Product" %}:</strong> {{vm.name}}
</p>
<div class="row">
<div class="col-sm-6">
{% comment %}
<p>
<span>{% trans "Period" %}</span>
<span class="pull-right">{{}}</span>
</p>
{% endcomment %}
<p>
<span>{% trans "Cores" %}</span>
{% if vm.cores %}
<span class="pull-right">{{vm.cores|floatformat}}</span>
{% else %}
<span class="pull-right">{{vm.cpu|floatformat}}</span>
{% endif %}
</p>
<p>
<span>{% trans "Memory" %}</span>
<span class="pull-right">{{vm.memory}} GB</span>
</p>
<p>
<span>{% trans "Disk space" %}</span>
<span class="pull-right">{{vm.disk_size}} GB</span>
</p>
<p>
<span>{% trans "Total" %}</span>
<span class="pull-right">{{vm.price}} CHF</span>
</p>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h3><b>{% trans "Order summary"%}</b></h3>
<hr>
<div class="content">
{% if request.session.specs %}
{% with request.session.specs as vm %}
<p><b>{% trans "Cores"%}</b>
<span class="pull-right">{{vm.cpu}}</span>
</p>
<hr>
<p><b>{% trans "Memory"%}</b>
<span class="pull-right">{{vm.memory}} GB</span>
</p>
<hr>
<p><b>{% trans "Disk space"%}</b>
<span class="pull-right">{{vm.disk_size}} GB</span>
</p>
<hr>
<p><b>{% trans "Configuration"%}</b>
<span class="pull-right">{{request.session.template.name}}</span>
</p>
<hr>
<h4>{% trans "Total"%}
<p class="pull-right">
<b>{{vm.price}} CHF</b>
<span class="dcl-price-month"> /{% trans "Month" %}
</span>
</p>
</h4>
{% endwith %}
{% else %}
<p><b>{% trans "Cores"%}</b>
<span class="pull-right">{{vm.cores}}</span>
</p>
<hr>
<p><b>{% trans "Memory"%}</b>
<span class="pull-right">{{vm.memory}} GB</span>
</p>
<hr>
<p><b>{% trans "Disk space"%}</b>
<span class="pull-right">{{vm.disk_size}} GB</span>
</p>
<hr>
<h4>{% trans "Total"%}<p class="pull-right"><b>{{vm.price}}
CHF</b><span
class="dcl-price-month"> /{% trans "Month" %}</span>
</p></h4>
{% endif %}
</div>
<br/>
{% if not order %}
<hr>
{% endif %}
</div>
{% if not order %}
<form method="post" id="virtual_machine_create_form">
{% csrf_token %}
<div class="row">
<div class="col-sm-8">
<p class="dcl-place-order-text">{% blocktrans with vm_price=request.session.specs.price %}By clicking "Place order" this plan will charge your credit card account with the fee of {{ vm_price }}CHF/month{% endblocktrans %}.</p>
<div class="dcl-place-order-text">{% blocktrans with vm_price=request.session.specs.price %}By clicking "Place order" this plan will charge your credit card account with the fee of {{ vm_price }}CHF/month{% endblocktrans %}.</div>
</div>
<div class="col-sm-4 content">
<button class="btn btn-info pull-right"
id="btn-create-vm"
data-href="{% url 'hosting:order-confirmation' %}"
data-toggle="modal"
data-target="#createvm-modal">
{% trans "Place order"%}
<div class="col-sm-4 order-confirm-btn text-right">
<button class="btn choice-btn" id="btn-create-vm" data-href="{% url 'hosting:order-confirmation' %}" data-toggle="modal" data-target="#createvm-modal">
{% trans "Place order" %}
</button>
</div>
</div>
</form>
{% endif %}
</div>
</div>
{% endif %}
{% endif %}
</div>
<!-- Create VM Modal -->
<div class="modal fade" id="createvm-modal" tabindex="-1" role="dialog"
aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close hidden" data-dismiss="modal"
aria-label="create-vm-close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="modal-icon">
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
<span class="sr-only">{% trans "Processing..." %}</span>
{% if order %}
<div class="text-center" style="margin-bottom: 50px;">
<a class="btn btn-vm-back" href="{% url 'hosting:orders' %}">{% trans "BACK TO LIST" %}</a>
</div>
{% endif %}
{% if not order %}
<!-- Create VM Modal -->
<div class="modal fade" id="createvm-modal" tabindex="-1" role="dialog"
aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close hidden" data-dismiss="modal"
aria-label="create-vm-close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<h4 class="modal-title" id="createvm-modal-title">
</h4>
<div class="modal-text" id="createvm-modal-body">
{% trans "Hold tight, we are processing your request" %}
</div>
<div class="modal-footer">
<div class="modal-body">
<div class="modal-icon">
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
<span class="sr-only">{% trans "Processing..." %}</span>
</div>
<h4 class="modal-title" id="createvm-modal-title">
</h4>
<div class="modal-text" id="createvm-modal-body">
{% trans "Hold tight, we are processing your request" %}
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- / Create VM Modal -->
<!-- / Create VM Modal -->
{% endif %}
<script type="text/javascript">
{% trans "Some problem encountered. Please try again later." as err_msg %}
var create_vm_error_message = '{{err_msg|safe}}.';
var create_vm_error_message = '{{err_msg|safe}}';
window.onload = function () {
var locale_date = moment.utc(document.getElementById("order-created_at").textContent, 'YYYY-MM-DD HH:mm').toDate();
@ -211,7 +195,12 @@
document.getElementById('order-created_at').innerHTML = locale_date;
};
</script>
{%endblock%}
{% block js_extra %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.3.5/jspdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script>
<script src="{% static 'hosting/js/html2pdf.js' %}"></script>
<script src="{% static 'hosting/js/order.js' %}"></script>
{% endblock js_extra %}

View file

@ -41,13 +41,15 @@
<td data-header="IPv6">{{vm.ipv6}}</td>
{% endif %}
<td data-header="{% trans 'Status' %}">
{% if vm.state == 'ACTIVE' %}
<span class="vm-status-active"><strong>{{vm.state|title}}</strong></span>
{% elif vm.state == 'FAILED' %}
<span class="vm-status-failed"><strong>{{vm.state|title}}</strong></span>
{% else %}
<span class="vm-status"><strong>{{vm.state|title}}</strong></span>
{% endif %}
<strong>
{% if vm.state == 'ACTIVE' %}
<span class="vm-status-active">{{vm.state|title}}</span>
{% elif vm.state == 'FAILED' %}
<span class="vm-status-failed">{{vm.state|title}}</span>
{% else %}
<span class="vm-status">{{vm.state|title}}</span>
{% endif %}
</strong>
</td>
<td class="text-right last-td">
<a class="btn btn-vm-detail" href="{% url 'hosting:virtual_machines' vm.vm_id %}">{% trans "View Detail" %}</a>

View file

@ -644,7 +644,7 @@ class OrdersHostingDetailView(LoginRequiredMixin,
model = HostingOrder
def get_object(self):
return HostingOrder.objects.filter(
return HostingOrder.objects.get(
pk=self.kwargs.get('pk')) if self.kwargs.get('pk') else None
def get_context_data(self, **kwargs):
@ -652,54 +652,79 @@ class OrdersHostingDetailView(LoginRequiredMixin,
context = super(DetailView, self).get_context_data(**kwargs)
obj = self.get_object()
owner = self.request.user
if 'specs' not in self.request.session:
return HttpResponseRedirect(
reverse('hosting:create_virtual_machine'))
if 'token' not in self.request.session:
return HttpResponseRedirect(reverse('hosting:payment'))
stripe_customer_id = self.request.session.get('customer')
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
stripe_utils = StripeUtils()
card_details = stripe_utils.get_card_details(customer.stripe_id,
self.request.session.get(
'token'))
if not card_details.get('response_object'):
msg = card_details.get('error')
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='failed_payment')
return HttpResponseRedirect(
reverse('hosting:payment') + '#payment_error')
if customer:
card_details = stripe_utils.get_card_details(
customer.stripe_id,
self.request.session.get('token')
)
else:
card_details = {}
if self.request.GET.get('page', '') == 'payment':
if self.request.GET.get('page') == 'payment':
context['page_header_text'] = _('Confirm Order')
else:
context['page_header_text'] = _('Invoice')
if obj is not None:
# invoice for previous order
try:
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
manager = OpenNebulaManager(
email=owner.email, password=owner.password
)
vm = manager.get_vm(obj.vm_id)
context['vm'] = VirtualMachineSerializer(vm).data
except WrongIdError:
messages.error(self.request,
'The VM you are looking for is unavailable at the moment. \
Please contact Data Center Light support.'
)
messages.error(
self.request,
_('The VM you are looking for is unavailable at the '
'moment. Please contact Data Center Light support.')
)
self.kwargs['error'] = 'WrongIdError'
context['error'] = 'WrongIdError'
except ConnectionRefusedError:
messages.error(self.request,
'In order to create a VM, you need to create/upload your SSH KEY first.'
)
messages.error(
self.request,
_('In order to create a VM, you need to create/upload '
'your SSH KEY first.')
)
elif not card_details.get('response_object'):
# new order, failed to get card details
context['failed_payment'] = True
context['card_details'] = card_details
else:
# new order, confirm payment
context['site_url'] = reverse('hosting:create_virtual_machine')
context['cc_last4'] = card_details.get('response_object').get(
'last4')
context['cc_brand'] = card_details.get('response_object').get(
'cc_brand')
context['vm'] = self.request.session.get('specs')
return context
def get(self, request, *args, **kwargs):
if not self.kwargs.get('pk'):
if 'specs' not in self.request.session:
return HttpResponseRedirect(
reverse('hosting:create_virtual_machine')
)
if 'token' not in self.request.session:
return HttpResponseRedirect(reverse('hosting:payment'))
self.object = self.get_object()
context = self.get_context_data(object=self.object)
if 'failed_payment' in context:
msg = context['card_details'].get('error')
messages.add_message(
self.request, messages.ERROR, msg,
extra_tags='failed_payment'
)
return HttpResponseRedirect(
reverse('hosting:payment') + '#payment_error'
)
return self.render_to_response(context)
def post(self, request):
template = request.session.get('template')
specs = request.session.get('specs')
@ -775,8 +800,8 @@ class OrdersHostingDetailView(LoginRequiredMixin,
'redirect': reverse('hosting:virtual_machines'),
'msg_title': str(_('Thank you for the order.')),
'msg_body': str(_('Your VM will be up and running in a few moments.'
' We will send you a confirmation email as soon as'
' it is ready.'))
' We will send you a confirmation email as soon as'
' it is ready.'))
}
return HttpResponse(json.dumps(response),