"),_addCloseButton($notify),("normal"===me.$options.size||"mini"===me.$options.size)&&(_addCloseOnClick($notify),_addDelay($notify)),me.$options.width&&$notify.css("width",_calculateWidth(me.$options.width)),$notify},_addCloseButton=function($el){me.$options.closable&&$('
×').click(function(){me.remove()}).appendTo($el)},_addCloseOnClick=function($el){me.$options.closeOnClick&&$el.click(function(){me.remove()})},_addDelay=function($el){if(me.$options.delay){if(me.$options.delayIndicator){var delay=$('
');$el.append(delay)}var time=0,interval=1e3/30,currentTime=(new Date).getTime(),timer=setInterval(function(){me.$options.continueDelayOnInactiveTab?time=(new Date).getTime()-currentTime:time+=interval;var width=100*time/me.$options.delay;width>=100&&(width=100,me.remove(),timer=clearInterval(timer)),me.$options.delayIndicator&&delay.find("div").css("width",width+"%")},interval);me.$options.pauseDelayOnHover&&$el.on("mouseenter.lobibox",function(){interval=0}).on("mouseleave.lobibox",function(){interval=1e3/30})}},_findTabToActivate=function($li){var $itemToActivate=$li.prev();return 0===$itemToActivate.length&&($itemToActivate=$li.next()),0===$itemToActivate.length?null:$itemToActivate},_calculateWidth=function(width){return width=Math.min($(window).outerWidth(),width)};this.remove=function(){me.$el.removeClass(me.$options.showClass).addClass(me.$options.hideClass);var parent=me.$el.parent(),wrapper=parent.closest(".lobibox-notify-wrapper-large"),href="#"+parent.attr("id"),$li=wrapper.find('>.lb-notify-tabs>li:has(a[href="'+href+'"])');return $li.addClass(Lobibox.notify.OPTIONS["class"]).addClass(me.$options.hideClass),setTimeout(function(){if("normal"===me.$options.size||"mini"===me.$options.size)me.$el.remove();else if("large"===me.$options.size){var $newLi=_findTabToActivate($li);$newLi&&_activateTab($newLi),$li.remove(),parent.remove()}var list=Lobibox.notify.list,ind=list.indexOf(me);list.splice(ind,1);var next=list[ind];next&&next.$options.showAfterPrevious&&next._init()},500),me},me._init=function(){var $notify=_createNotify();if("mini"===me.$options.size&&$notify.addClass("notify-mini"),"string"==typeof me.$options.position){var $wrapper=_createNotifyWrapper();_appendInWrapper($notify,$wrapper),$wrapper.hasClass("center")&&$wrapper.css("margin-left","-"+$wrapper.width()/2+"px")}else $("body").append($notify),$notify.css({position:"fixed",left:me.$options.position.left,top:me.$options.position.top});if(me.$el=$notify,me.$options.sound){var snd=new Audio(me.$options.sound);snd.play()}me.$options.rounded&&me.$el.addClass("rounded"),me.$el.on("click.lobibox",function(ev){me.$options.onClickUrl&&(window.location.href=me.$options.onClickUrl),me.$options.onClick&&"function"==typeof me.$options.onClick&&me.$options.onClick.call(me,ev)}),me.$el.data("lobibox",me)},this.$type=type,this.$options=_processInput(options),me.$options.showAfterPrevious&&0!==Lobibox.notify.list.length||this._init()};Lobibox.notify=function(type,options){if(["default","info","warning","error","success"].indexOf(type)>-1){var lobibox=new LobiboxNotify(type,options);return Lobibox.notify.list.push(lobibox),lobibox}},Lobibox.notify.list=[],Lobibox.notify.closeAll=function(){var list=Lobibox.notify.list;for(var i in list)list[i].remove()},Lobibox.notify.DEFAULTS={title:!0,size:"normal",soundPath:"sounds/",soundExt:".ogg",showClass:"fadeInDown",hideClass:"zoomOut",icon:!0,msg:"",img:null,closable:!0,hideCloseButton:!1,delay:5e3,delayIndicator:!0,closeOnClick:!0,width:400,sound:!0,position:"bottom right",iconSource:"bootstrap",rounded:!1,messageHeight:60,pauseDelayOnHover:!0,onClickUrl:null,showAfterPrevious:!1,continueDelayOnInactiveTab:!0,onClick:null},Lobibox.notify.OPTIONS={"class":"animated-fast",large:{width:500,messageHeight:96},mini:{"class":"notify-mini",messageHeight:32},"default":{"class":"lobibox-notify-default",title:"Default",sound:!1},success:{"class":"lobibox-notify-success",title:"Success",sound:"sound2"},error:{"class":"lobibox-notify-error",title:"Error",sound:"sound4"},warning:{"class":"lobibox-notify-warning",title:"Warning",sound:"sound5"},info:{"class":"lobibox-notify-info",title:"Information",sound:"sound6"},icons:{bootstrap:{success:"glyphicon glyphicon-ok-sign",error:"glyphicon glyphicon-remove-sign",warning:"glyphicon glyphicon-exclamation-sign",info:"glyphicon glyphicon-info-sign"},fontAwesome:{success:"fa fa-check-circle",error:"fa fa-times-circle",warning:"fa fa-exclamation-circle",info:"fa fa-info-circle"}}}}();
--------------------------------------------------------------------------------
/public/stylesheets/style.css:
--------------------------------------------------------------------------------
1 | .section-colored, .section { padding: 50px 0; }
2 | .section-colored { background-color: #e1e1e1; }
3 |
4 | /* Pagination Styles */
5 | .pagination-nav { margin: 30px 0 20px 0; }
6 | .pagination-nav ul {margin: 0; padding: 0;}
7 | .pagination-nav ul li {display: inline-block; margin: 3px; padding: 6px 10px; background: #FFF; color: black; border-radius: 2px; }
8 | .pagination-nav ul li.active:hover {cursor: pointer; background: #18bc9c; color: white; }
9 | .pagination-nav ul li.inactive {background: #e8e8e8;}
10 | .pagination-nav ul li.selected {background: #18bc9c; color: white;}
11 |
12 | /* imageviewer CSS */
13 | .imageviewer {width: 100%; position: relative; height: 450px; background: #2c3e50; }
14 | .imageviewer .image-container {position: absolute; top: 0; left: 0; right: 0; bottom: 50px; }
15 | .imageviewer .prev, .imageviewer .next {position: absolute; margin-top: -20px; top: 50%; cursor: pointer; font-size: 30px; color: rgba(255,255,255,0.6); }
16 | .imageviewer .prev {left: 20px;}
17 | .imageviewer .next {right: 20px;}
18 | .imageviewer .footer-info {position: absolute; background: #fff; height: 50px; width: 100%; left: 0; bottom: 0; line-height: 50px; text-align: center; color: #333;}
19 |
20 | /* Wave Box Effect */
21 | .wave-box-effect{height:100%; width:20px;position:absolute;background:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZjRmNCIgc3RvcC1vcGFjaXR5PSIwIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiIHN0b3Atb3BhY2l0eT0iMC43MSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);background:-moz-linear-gradient(left, rgba(255,244,244,0) 0%, rgba(255,255,255,0.71) 100%);background:-webkit-gradient(linear, left top, right top, color-stop(0%,rgba(255,244,244,0)), color-stop(100%,rgba(255,255,255,0.71)));background:-webkit-linear-gradient(left, rgba(255,244,244,0) 0%,rgba(255,255,255,0.71) 100%);background:-o-linear-gradient(left, rgba(255,244,244,0) 0%,rgba(255,255,255,0.71) 100%);background:-ms-linear-gradient(left, rgba(255,244,244,0) 0%,rgba(255,255,255,0.71) 100%);background:linear-gradient(to right, rgba(255,244,244,0) 0%,rgba(255,255,255,0.71) 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#00fff4f4', endColorstr='#b5ffffff',GradientType=1 )}
22 | .wave-box-wrapper{ position: relative;clear:both; min-height: 300px;}
23 | .wave-box{display:none;position:absolute;top:0px;left:0px;width:100%;height:100%;z-index:100;border:solid 1px ccc;background:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIgc3RvcC1vcGFjaXR5PSIwLjMiLz4KICAgIDxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIgc3RvcC1vcGFjaXR5PSIwLjQiLz4KICA8L2xpbmVhckdyYWRpZW50PgogIDxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIGZpbGw9InVybCgjZ3JhZC11Y2dnLWdlbmVyYXRlZCkiIC8+Cjwvc3ZnPg==);background:-moz-linear-gradient(top, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.4) 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,0,0,0.3)), color-stop(100%,rgba(0,0,0,0.4)));background:-webkit-linear-gradient(top, rgba(0,0,0,0.3) 0%,rgba(0,0,0,0.4) 100%);background:-o-linear-gradient(top, rgba(0,0,0,0.3) 0%,rgba(0,0,0,0.4) 100%);background:-ms-linear-gradient(top, rgba(0,0,0,0.3) 0%,rgba(0,0,0,0.4) 100%);background:linear-gradient(to bottom, rgba(0,0,0,0.3) 0%,rgba(0,0,0,0.4) 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#4d000000', endColorstr='#66000000',GradientType=0 )}
24 |
25 | /* CKEditor Overrides */
26 | .override .cke_top, .override .cke_bottom { background-image: none; background: #f5f5f5; border-bottom: 0; border-top: 0; }
27 | .override .cke_chrome { box-shadow: none; border: 1px solid #ccc; }
28 | .override .cke_toolgroup, .override a.cke_combo_button{ border-bottom-color: none; background-image: none; box-shadow: none; border: 1px solid #ccc; background: #fff; }
29 |
30 | /* Padding 10px */
31 | .override .p-d { padding: 10px; }
32 | .override .p-t { padding-top: 10px; }
33 | .override .p-b { padding-bottom: 10px; }
34 | .override .p-l { padding-left: 10px; }
35 | .override .p-r { padding-right: 10px; }
36 | .override .p-lr { padding-left: 10px; padding-right: 10px; }
37 | .override .p-tb { padding-top: 10px; padding-bottom: 10px; }
38 | .override .p-0 { padding: 0; }
39 |
40 | /* Padding 20px */
41 | .override .pl-d { padding: 20px; }
42 | .override .pl-t { padding-top: 20px; }
43 | .override .pl-b { padding-bottom: 20px; }
44 | .override .pl-l { padding-left: 20px; }
45 | .override .pl-r { padding-right: 20px; }
46 | .override .pl-lr { padding-left: 20px; padding-right: 20px; }
47 | .override .pl-tb { padding-top: 20px; padding-bottom: 20px; }
48 |
49 | /* Margin 10px */
50 | .override .m-0 { margin: 0; }
51 | .override .m-d { margin: 10px; }
52 | .override .m-t { margin-top: 10px; }
53 | .override .m-b { margin-bottom: 10px; }
54 | .override .m-l { margin-left: 10px; }
55 | .override .m-r { margin-right: 10px; }
56 | .override .m-lr { margin-left: 10px; margin-right: 10px; }
57 | .override .m-tb { margin-top: 10px; margin-bottom: 10px; }
58 |
59 | /* Margin 20px */
60 | .override .ml-d { margin: 20px; }
61 | .override .ml-t { margin-top: 20px; }
62 | .override .ml-b { margin-bottom: 20px; }
63 | .override .ml-l { margin-left: 20px; }
64 | .override .ml-r { margin-right: 20px; }
65 | .override .ml-lr { margin-left: 20px; margin-right: 20px; }
66 | .override .ml-tb { margin-top: 20px; margin-bottom: 20px; }
67 |
68 | /* Borders */
69 | .override .b-0 { border: 0; }
70 | .override .b-d { border: 1px solid #ddd; }
71 | .override .b-t { border-top: 1px solid #ddd; }
72 | .override .b-r { border-right: 1px solid #ddd; }
73 | .override .b-b { border-bottom: 1px solid #ddd; }
74 | .override .b-l { border-left: 1px solid #ddd; }
75 | .override .bl-t, .bl-t-nh { border-top: 4px solid #ddd; }
76 | .override .bl-t:hover, .bl-t-h { border-top: 4px solid #1C79DA; }
77 |
78 | /* Displays */
79 | .override .d-i { display: inline; }
80 | .override .d-ib { display: inline-block; }
81 | .override .d-b { display: block; }
82 | .override .d-n { display: none; }
83 | .override .d-tc { display: table-cell; }
84 |
85 | /* Text Color Accents */
86 | .override .a-1 { color: #F36519; }
87 |
88 | /* Floats */
89 | .override .fl-l { float: left; }
90 | .override .fl-r { float: right; }
91 | .override .fl-n { float: none; }
92 |
93 | /* Font Styles */
94 | .override .f-b { font-weight: bold; }
95 | .override .f-i { font-style: italic; }
96 | .override .f-n { font-style: none; font-weight: none; text-decoration: none; }
97 |
98 | /* Positions */
99 | .override .pn-r { position: relative; }
100 | .override .pn-a { position: absolute; }
101 | .override .pn-s { position: static; }
102 |
103 | .override .c-p { cursor: pointer; }
104 |
105 | /* Change bootstrap slider transition effect to Fade */
106 | .carousel-fade .carousel-inner .item { opacity: 0; -webkit-transition-property: opacity; -moz-transition-property: opacity; -o-transition-property: opacity; transition-property: opacity; }
107 | .carousel-fade .carousel-inner .active { opacity: 1; }
108 | .carousel-fade .carousel-inner .active.left,
109 | .carousel-fade .carousel-inner .active.right { left: 0; opacity: 0; z-index: 1; }
110 | .carousel-fade .carousel-inner .next.left,
111 | .carousel-fade .carousel-inner .prev.right { opacity: 1; }
112 | .carousel-fade .carousel-control { z-index: 2; }
--------------------------------------------------------------------------------
/routes/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 |
4 | router.use('/products', require('./product'));
5 | router.use('/user', require('./user'));
6 | router.use('/user/products', require('./user_product'));
7 |
8 | /* GET home page. */
9 | router.get('/', function(req, res, next) {
10 | res.render('front/index', { title: 'NodeJS with MongoDB' });
11 | });
12 |
13 | module.exports = router;
14 |
--------------------------------------------------------------------------------
/routes/product.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 | var async = require('async');
4 |
5 | /* GET products page. */
6 | router.get('/', function(req, res, next) {
7 |
8 | var collection = req.db.get('products');
9 | collection.find({},{},function(e,docs){
10 | res.render('front/products', {
11 | 'products' : docs,
12 | 'title' : 'Products'
13 | });
14 | });
15 | });
16 |
17 | router.post('/view-front', function(req, res){
18 |
19 | /* Set our internal DB variable */
20 | var db = req.db;
21 |
22 | /* Set our collection */
23 | products = db.get('products');
24 |
25 | pag_content = '';
26 | pag_navigation = '';
27 |
28 | page = parseInt(req.body.data.page); /* Page we are currently at */
29 | name = req.body.data.name; /* Name of the column name we want to sort */
30 | sort = req.body.data.sort == 'ASC' ? 1 : -1; /* Order of our sort (DESC or ASC) */
31 | max = parseInt(req.body.data.max); /* Number of items to display per page */
32 | search = req.body.data.search; /* Keyword provided on our search box */
33 |
34 | cur_page = page;
35 | page -= 1;
36 | per_page = max ? max : 16;
37 | previous_btn = true;
38 | next_btn = true;
39 | first_btn = true;
40 | last_btn = true;
41 | start = page * per_page;
42 |
43 | where_search = {};
44 |
45 | /* Check if there is a string inputted on the search box */
46 | if( search != '' ){
47 | /* If a string is inputted, include an additional query logic to our main query to filter the results */
48 | var filter = new RegExp(search, 'i');
49 | where_search = {
50 | '$or' : [
51 | {'name' : filter},
52 | {'price' : filter},
53 | ]
54 | }
55 | }
56 |
57 | var all_items = '';
58 | var count = '';
59 | var sort_query = {};
60 |
61 | /* We use async task to make sure we only return data when all queries completed successfully */
62 | async.parallel([
63 | function(callback) {
64 | /* Use name and sort variables as field names */
65 | sort_query[name] = sort;
66 |
67 | /* Retrieve all the posts */
68 | products.find( where_search, {
69 | limit: per_page,
70 | skip: start,
71 | sort: sort_query
72 |
73 | }, function(err, docs){
74 | if (err) throw err;
75 | // console.log(docs);
76 | all_items = docs;
77 | callback();
78 |
79 | });
80 | },
81 | function(callback) {
82 | products.count(where_search, function(err, doc_count){
83 | if (err) throw err;
84 | // console.log(count);
85 | count = doc_count;
86 | callback();
87 | });
88 | }
89 | ], function(err) { //This is the final callback
90 | /* Check if our query returns anything. */
91 | if( count ){
92 | for (var key in all_items) {
93 | pag_content += '
' +
94 | '
' +
95 | '
' +
96 | all_items[key].name +
97 | '
' +
98 | '
' +
99 | '

' +
100 | '
' +
101 | '
' +
102 | '
' +
103 | '
Price
' +
104 | '
$' + parseFloat(all_items[key].price).toFixed(2) + '
' +
105 | '
' +
106 | '
' +
107 | '
' +
108 | '
On Stock
' +
109 | '
' + all_items[key].stock + '
' +
110 | '
' +
111 | '
' +
112 | '
' +
113 | '' +
116 | '
' +
117 | '
';
118 | }
119 | }
120 |
121 | pag_content = pag_content + "
";
122 |
123 | no_of_paginations = Math.ceil(count / per_page);
124 |
125 | if (cur_page >= 7) {
126 | start_loop = cur_page - 3;
127 | if (no_of_paginations > cur_page + 3)
128 | end_loop = cur_page + 3;
129 | else if (cur_page <= no_of_paginations && cur_page > no_of_paginations - 6) {
130 | start_loop = no_of_paginations - 6;
131 | end_loop = $no_of_paginations;
132 | } else {
133 | end_loop = no_of_paginations;
134 | }
135 | } else {
136 | start_loop = 1;
137 | if (no_of_paginations > 7)
138 | end_loop = 7;
139 | else
140 | end_loop = no_of_paginations;
141 | }
142 |
143 | pag_navigation += "
";
144 |
145 | if (first_btn && cur_page > 1) {
146 | pag_navigation += "- First
";
147 | } else if (first_btn) {
148 | pag_navigation += "- First
";
149 | }
150 |
151 | if (previous_btn && cur_page > 1) {
152 | pre = cur_page - 1;
153 | pag_navigation += "- Previous
";
154 | } else if (previous_btn) {
155 | pag_navigation += "- Previous
";
156 | }
157 | for (i = start_loop; i <= end_loop; i++) {
158 |
159 | if (cur_page == i)
160 | pag_navigation += "- " + i + "
";
161 | else
162 | pag_navigation += "- " + i + "
";
163 | }
164 |
165 | if (next_btn && cur_page < no_of_paginations) {
166 | nex = cur_page + 1;
167 | pag_navigation += "- Next
";
168 | } else if (next_btn) {
169 | pag_navigation += "- Next
";
170 | }
171 |
172 | if (last_btn && cur_page < no_of_paginations) {
173 | pag_navigation += "- Last
";
174 | } else if (last_btn) {
175 | pag_navigation += "- Last
";
176 | }
177 |
178 | pag_navigation = pag_navigation + "
";
179 |
180 | var response = {
181 | 'content': pag_content,
182 | 'navigation' : pag_navigation
183 | };
184 |
185 | res.send(response);
186 |
187 | });
188 |
189 | });
190 |
191 | /* GET Single Product Data. */
192 | router.get('/:id', function(req, res, next) {
193 |
194 | var db = req.db;
195 | item_id = req.params.id
196 | item_id_check = item_id.match(/^[0-9a-fA-F]{24}$/);
197 | products = db.get('products');
198 | item_details = '';
199 |
200 | /* Check if the object id is valid */
201 | if( item_id_check ){
202 | async.parallel([
203 | function(callback) {
204 | products.findOne(item_id).then((doc) => {
205 | item_details = doc;
206 | callback();
207 | });
208 | }
209 | ], function(err) {
210 |
211 | var images_array = [];
212 | var len = item_details.images.length;
213 | for (var i = 0; i < len; i++) {
214 | images_array.push({
215 | small: '/images/uploads/' + item_details.images[i],
216 | big: '/images/uploads/' + item_details.images[i]
217 | });
218 | }
219 |
220 | res.render('front/products-single', {
221 | title: item_details.name,
222 | item: item_details,
223 | item_images: JSON.stringify(images_array)
224 | });
225 | });
226 |
227 | } else {
228 | res.status(404).send('Invalid Item ID');
229 | }
230 | });
231 |
232 | module.exports = router;
--------------------------------------------------------------------------------
/routes/user.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 | var md5 = require('crypto-md5');
4 | var async = require('async');
5 | var passport = require('passport');
6 | var flash = require('connect-flash');
7 |
8 | /* GET login page. */
9 | router.get('/login', function(req, res, next) {
10 | if (req.isAuthenticated()) return res.redirect('/user/account');
11 |
12 | res.render('user/login', {
13 | message: req.flash('error'),
14 | title: 'Login'
15 | });
16 | });
17 |
18 | router.post('/login', passport.authenticate('local', {
19 | successRedirect: '/',
20 | failureRedirect: '/user/login',
21 | failureFlash: true
22 | })
23 | );
24 |
25 | /* GET registration page. */
26 | router.get('/register', function(req, res, next) {
27 | if (req.isAuthenticated()) return res.redirect('/user/account');
28 |
29 | res.render('user/register', { title: 'Register' });
30 | });
31 |
32 | /* POST Create User Account. */
33 | router.post('/register/create', function(req, res, next) {
34 | var db = req.db;
35 | users = db.get('users');
36 |
37 | async.parallel([
38 | function(callback) {
39 | users.count({'email':req.body.email}, function (error, count) {
40 | user_exists = count;
41 | callback();
42 | });
43 | }
44 | ], function(err) {
45 | if(req.body.email == '' || req.body.password == ''){
46 | res.send({
47 | 'status': 0,
48 | 'message': 'Please fill-up all required fields'
49 | });
50 | } else if(req.body.password != req.body.password2) {
51 | res.send({
52 | 'status': 0,
53 | 'message': 'Passwords do not match'
54 | });
55 | } else if(user_exists) {
56 | res.send({
57 | 'status': 0,
58 | 'message': 'User ' + req.body.email + ' already exists'
59 | });
60 | } else {
61 | users.insert({
62 | 'email' : req.body.email,
63 | 'password' : md5(req.body.password),
64 | 'first_name' : req.body.first_name,
65 | 'last_name' : req.body.last_name,
66 | 'phone_number' : req.body.phone_number
67 |
68 | }, function (err, user) {
69 | if (err) {
70 | res.send({
71 | 'status': 0,
72 | 'message': err
73 | });
74 | } else {
75 | /* Login the user */
76 | req.login(user, function(err) {
77 | if (err) {
78 | res.send({
79 | 'status': 0,
80 | 'message': err
81 | });
82 | } else{
83 | res.send({
84 | 'status': 1
85 | });
86 | }
87 | });
88 | }
89 | });
90 | }
91 | });
92 | });
93 |
94 | /* GET User Account page. */
95 | router.get('/account', function(req, res, next) {
96 | if (!req.isAuthenticated()) return res.redirect('/user/login');
97 |
98 | var db = req.db;
99 | users = db.get('users');
100 |
101 | async.parallel([
102 | function(callback) {
103 | users.findOne(req.user._id.toString()).then((doc) => {
104 | user = doc;
105 | callback();
106 | });
107 | }
108 | ], function(err) {
109 | res.render('user/account', {
110 | title: 'Account | ' + user.email,
111 | user: user
112 | });
113 | });
114 | });
115 |
116 | /* POST Update User Account Info. */
117 | router.post('/account/update', function(req, res, next) {
118 |
119 | var db = req.db;
120 | users = db.get('users');
121 | posted_data = req.body;
122 | post_fields_array = {};
123 |
124 | if( posted_data.old_password != '' || posted_data.password_confirm != '' || posted_data.password != '' ){
125 | if( posted_data.password_confirm != posted_data.password ){
126 | return res.send({
127 | 'status' : 0,
128 | 'message' : 'New password do not match.'
129 | });
130 | } else if( md5(posted_data.old_password) != req.user.password ){
131 | return res.send({
132 | 'status' : 0,
133 | 'message' : 'Incorrect old password.'
134 | });
135 | }
136 | }
137 |
138 | async.parallel([
139 | function(callback) {
140 | /* Let's build an array out of available values that are posted */
141 | for (key in posted_data) {
142 | if( key != 'email' && key != 'old_password' && key != 'password_confirm' && key != 'password' ){
143 | post_fields_array[key] = posted_data[key];
144 | }
145 | }
146 |
147 | /* Check if we have a password to include in our update */
148 | if( posted_data.password != '' ){
149 | post_fields_array['password'] = md5(posted_data.password);
150 | }
151 |
152 | callback();
153 | },
154 | function(callback) {
155 | /* Update user profile */
156 | users.update(req.user._id, {
157 | '$set': post_fields_array
158 | });
159 |
160 | callback();
161 | }
162 | ], function(err) {
163 | if(err){
164 | return res.send({
165 | 'status' : 0,
166 | 'message' : 'Update failed, please try again.'
167 | });
168 | }
169 | res.send({
170 | 'status' : 1,
171 | 'message' : 'Account successfully updated'
172 | });
173 | });
174 | });
175 |
176 | /* Handle Logout */
177 | router.get('/logout', function(req, res) {
178 | req.logout();
179 | res.redirect('/');
180 | });
181 |
182 | module.exports = router;
--------------------------------------------------------------------------------
/routes/user_product.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 | var async = require('async');
4 | var fs = require('fs');
5 | var path = require('path');
6 | var multer = require('multer');
7 | var crypto = require('crypto');
8 | var storage = multer.diskStorage({
9 | destination: './public/images/uploads/', /* upload path */
10 | filename: function (req, file, cb) {
11 | /* Format: random hex using crypto + image extension */
12 | crypto.pseudoRandomBytes(16, function (err, raw){
13 | cb(null, raw.toString('hex') + path.extname(file.originalname));
14 | });
15 | }
16 | });
17 | var upload_image = multer({
18 | storage: storage,
19 | fileFilter: function (req, file, cb) {
20 | var allowed_extensions = ['.jpg', '.png', '.gif', '.bmp', '.jpeg'];
21 | var file_extension = path.extname(file.originalname);
22 | /* Check if file extension is valid */
23 | if(allowed_extensions.indexOf(file_extension) == -1){
24 | console.log(file.originalname + ' is not a valid file type');
25 | /* Do not upload if file is not an image */
26 | return cb(null, false);
27 | }
28 | cb(null, true)
29 | }
30 | });
31 |
32 | /* GET User Products page. */
33 | router.get('/', function(req, res, next) {
34 | if (!req.isAuthenticated()) return res.redirect('/user/login');
35 |
36 | res.render('user/products', { title: 'My Products' });
37 | });
38 |
39 | /* Serve the products. */
40 | router.post('/view', function(req, res){
41 |
42 | /* Set our internal DB variable */
43 | var db = req.db;
44 |
45 | /* Set our collection */
46 | products = db.get('products');
47 |
48 | pag_content = '';
49 | pag_navigation = '';
50 |
51 | page = parseInt(req.body.data.page); /* Page we are currently at */
52 | name = req.body.data.th_name; /* Name of the column name we want to sort */
53 | sort = req.body.data.th_sort == 'ASC' ? 1 : -1; /* Order of our sort (DESC or ASC) */
54 | max = parseInt(req.body.data.max); /* Number of items to display per page */
55 | search = req.body.data.search; /* Keyword provided on our search box */
56 |
57 | cur_page = page;
58 | page -= 1;
59 | per_page = max ? max : 16;
60 | previous_btn = true;
61 | next_btn = true;
62 | first_btn = true;
63 | last_btn = true;
64 | start = page * per_page;
65 |
66 | where_search = {};
67 |
68 | /* Check if there is a string inputted on the search box */
69 | if( search != '' ){
70 | /* If a string is inputted, include an additional query logic to our main query to filter the results */
71 | var filter = new RegExp(search, 'i');
72 | where_search = {
73 | '$or' : [
74 | {'name' : filter},
75 | {'price' : filter},
76 | ]
77 | }
78 | }
79 |
80 | /* Only query for items owned by currently logged in user */
81 | var where_author = {
82 | '$and' : [
83 | {'author' : req.user._id.toString()}
84 | ]
85 | }
86 |
87 | var where = {};
88 | for ( var attrname in where_author ) { where[attrname] = where_author[attrname]; }
89 | for ( var attrname in where_search ) { where[attrname] = where_search[attrname]; }
90 |
91 | var all_items = '';
92 | var count = '';
93 | var sort_query = {};
94 |
95 | /* We use async task to make sure we only return data when all queries completed successfully */
96 | async.parallel([
97 | function(callback) {
98 | /* Use name and sort variables as field names */
99 | sort_query[name] = sort;
100 |
101 | /* Retrieve all the posts */
102 | products.find( where, {
103 | limit: per_page,
104 | skip: start,
105 | sort: sort_query
106 |
107 | }, function(err, docs){
108 | if (err) throw err;
109 | // console.log(docs);
110 | all_items = docs;
111 | callback();
112 |
113 | });
114 | },
115 | function(callback) {
116 | products.count(where, function(err, doc_count){
117 | if (err) throw err;
118 | // console.log(count);
119 | count = doc_count;
120 | callback();
121 | });
122 | }
123 | ], function(err) { //This is the final callback
124 | /* Check if our query returns anything. */
125 | if( count ){
126 | for (var key in all_items) {
127 | pag_content += '
' +
128 | ' | ' +
129 | '' + all_items[key].name + ' | ' +
130 | '$' + all_items[key].price + ' | ' +
131 | '' + all_items[key].status + ' | ' +
132 | '' + all_items[key].date + ' | ' +
133 | '' + all_items[key].stock + ' | ' +
134 | '' +
135 | ' ' +
136 | '' +
137 | ' | ' +
138 | '
';
139 | }
140 | } else {
141 | pag_content += '
No Products Found | ';
142 | }
143 |
144 | pag_content = pag_content + "
";
145 |
146 | no_of_paginations = Math.ceil(count / per_page);
147 |
148 | if (cur_page >= 7) {
149 | start_loop = cur_page - 3;
150 | if (no_of_paginations > cur_page + 3)
151 | end_loop = cur_page + 3;
152 | else if (cur_page <= no_of_paginations && cur_page > no_of_paginations - 6) {
153 | start_loop = no_of_paginations - 6;
154 | end_loop = $no_of_paginations;
155 | } else {
156 | end_loop = no_of_paginations;
157 | }
158 | } else {
159 | start_loop = 1;
160 | if (no_of_paginations > 7)
161 | end_loop = 7;
162 | else
163 | end_loop = no_of_paginations;
164 | }
165 |
166 | pag_navigation += "
";
167 |
168 | if (first_btn && cur_page > 1) {
169 | pag_navigation += "- First
";
170 | } else if (first_btn) {
171 | pag_navigation += "- First
";
172 | }
173 |
174 | if (previous_btn && cur_page > 1) {
175 | pre = cur_page - 1;
176 | pag_navigation += "- Previous
";
177 | } else if (previous_btn) {
178 | pag_navigation += "- Previous
";
179 | }
180 | for (i = start_loop; i <= end_loop; i++) {
181 |
182 | if (cur_page == i)
183 | pag_navigation += "- " + i + "
";
184 | else
185 | pag_navigation += "- " + i + "
";
186 | }
187 |
188 | if (next_btn && cur_page < no_of_paginations) {
189 | nex = cur_page + 1;
190 | pag_navigation += "- Next
";
191 | } else if (next_btn) {
192 | pag_navigation += "- Next
";
193 | }
194 |
195 | if (last_btn && cur_page < no_of_paginations) {
196 | pag_navigation += "- Last
";
197 | } else if (last_btn) {
198 | pag_navigation += "- Last
";
199 | }
200 |
201 | pag_navigation = pag_navigation + "
";
202 |
203 | var response = {
204 | 'content': pag_content,
205 | 'navigation' : pag_navigation
206 | };
207 |
208 | res.send(response);
209 |
210 | });
211 |
212 | });
213 |
214 | /* GET User Products Add page. */
215 | router.get('/add', function(req, res, next) {
216 | if (!req.isAuthenticated()) return res.redirect('/user/login');
217 |
218 | res.render('user/products-add', { title: 'Add Product' });
219 | });
220 |
221 | /* Handle POST request to Add Item */
222 | router.post('/create', function(req, res) {
223 |
224 | /* Set our internal DB variable */
225 | var db = req.db;
226 | products = db.get('products');
227 |
228 | /* Submit to the DB */
229 | products.insert({
230 | 'name' : req.body.name,
231 | 'author' : req.user._id.toString(),
232 | 'content' : req.body.content,
233 | 'excerpt' : req.body.excerpt,
234 | 'price' : parseFloat(req.body.price),
235 | 'status' : req.body.status,
236 | 'stock' : parseInt(req.body.stock),
237 | 'date' : req.body.date
238 | }, function (err, doc) {
239 | if (err) {
240 | res.send(0); /* If it failed, return 0 (error) */
241 | } else {
242 | res.send(doc._id); /* Return document ID if insert was successfull */
243 | }
244 | });
245 | });
246 |
247 | /* GET User Products Edit page. */
248 | router.get('/edit/:id', function(req, res, next) {
249 | if (!req.isAuthenticated()) return res.redirect('/user/login');
250 |
251 | var db = req.db;
252 | item_id = req.params.id
253 | item_id_check = item_id.match(/^[0-9a-fA-F]{24}$/);
254 | products = db.get('products');
255 | item_details = '';
256 |
257 | /* Check if the object id is valid */
258 | if( item_id_check ){
259 | async.parallel([
260 | function(callback) {
261 | products.findOne(item_id).then((doc) => {
262 | item_details = doc;
263 | callback();
264 | });
265 | }
266 | ], function(err) {
267 | /* Make sure the current user owns the item */
268 | if( req.user._id.toString() == item_details.author){
269 | res.render('user/products-edit', {
270 | title: 'Edit Product',
271 | item: item_details
272 | });
273 | } else {
274 | res.status(404).send('You are not allowed to edit that item');
275 | }
276 | });
277 |
278 | } else {
279 | res.status(404).send('Invalid Item ID');
280 | }
281 | });
282 |
283 | router.post('/update', upload_image.array('images'), function(req, res, next){
284 | /* Set our internal DB variable */
285 | var db = req.db;
286 | products = db.get('products');
287 |
288 | products.update(req.body.id, {
289 | '$set': {
290 | 'name' : req.body.name,
291 | 'content' : req.body.content,
292 | 'excerpt' : req.body.excerpt,
293 | 'price' : parseFloat(req.body.price),
294 | 'status' : req.body.status,
295 | 'stock' : parseInt(req.body.stock),
296 | 'date' : req.body.date
297 | }
298 | }, function (err, doc) {
299 | if(err) {
300 | /* If it failed, return error */
301 | res.send({
302 | 'status' : 0,
303 | 'message' : 'There was a problem updating the information on the database.'
304 | });
305 | } else {
306 | if(req.files){
307 | var uploads = req.files;
308 | var uploaded_images = [];
309 |
310 | /* Save image(s) to database */
311 | for (var key in uploads) {
312 | if (uploads.hasOwnProperty(key)) {
313 | products.update(req.body.id, {
314 | '$addToSet' : {
315 | 'images' : uploads[key].filename
316 | }
317 | });
318 | uploaded_images.push(uploads[key].filename);
319 | }
320 | }
321 | }
322 |
323 | /* And forward to success page */
324 | res.send({
325 | 'status' : 1,
326 | 'images' : uploaded_images,
327 | 'message' : 'Item successfully updated'
328 | });
329 | }
330 | });
331 |
332 | console.log(req.files);
333 |
334 | });
335 |
336 | /* Handle POST request to Delete Item */
337 | router.post('/delete', function(req, res) {
338 | /* Set our internal DB variable */
339 | var db = req.db;
340 | item_id = req.body.item_id
341 | item_id_check = item_id.match(/^[0-9a-fA-F]{24}$/);
342 | products = db.get('products');
343 | item_images = '';
344 |
345 | /* Check if the object id is valid */
346 | if( item_id_check ){
347 | async.parallel([
348 | function(callback) {
349 | products.findOne(item_id).then((doc) => {
350 | /* Delete all images of this product */
351 | var item_images = doc.images;;
352 | var item_images_count = item_images.length;
353 | for (var i = 0; i < item_images_count; i++) {
354 | fs.unlink(uploads_dir + item_images[i], (err) => {
355 | if (err) throw err;
356 | });
357 | }
358 | callback();
359 | });
360 |
361 | }
362 | ], function(err) {
363 | /* Delete item data from the database */
364 | products.remove({'_id': item_id}, function(err, result){
365 | if(result == 1){
366 | res.send('1');
367 | } else {
368 | res.send('0');
369 | }
370 | });
371 | });
372 |
373 | } else {
374 | res.status(404).send('Invalid Item ID');
375 | }
376 |
377 | });
378 |
379 | /* Set as featured image */
380 | router.post('/image/set-featured', function(req, res){
381 | /* Set our internal DB variable */
382 | var db = req.db;
383 | products = db.get('products');
384 |
385 | products.update(req.body.item_id, {
386 | '$set' : {
387 | 'featured_image' : req.body.image
388 | }
389 | }, function (err, doc) {
390 | if(doc){
391 | res.send({
392 | 'status': 1,
393 | 'message': 'Image successfully set as featured'
394 | });
395 | } else {
396 | res.send({
397 | 'status': 0,
398 | 'message': err
399 | });
400 | }
401 | });
402 | });
403 |
404 | /* Delete image */
405 | router.post('/image/unset', function(req, res){
406 |
407 | fs.unlink(uploads_dir + req.body.image, (err) => {
408 | if (err) throw err;
409 |
410 | /* Set our internal DB variable */
411 | var db = req.db;
412 | products = db.get('products');
413 |
414 | products.update(req.body.item_id, {
415 | '$pull' : {
416 | 'images' : req.body.image
417 | }
418 | }, function (err, doc) {
419 | if(doc){
420 | res.send({
421 | 'status': 1,
422 | 'message': 'Image successfully deleted'
423 | });
424 | } else {
425 | res.send({
426 | 'status': 0,
427 | 'message': err
428 | });
429 | }
430 | });
431 | });
432 |
433 | });
434 |
435 |
436 | module.exports = router;
--------------------------------------------------------------------------------
/views/error.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | .container
5 | h1= message
6 | h2= error.status
7 | pre #{error.stack}
8 |
--------------------------------------------------------------------------------
/views/front/index.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | .container
5 | #products-carousel.carousel.slide.carousel-fade(data-ride='carousel')
6 | ol.carousel-indicators
7 | li.active(data-target='#products-carousel', data-slide-to='0')
8 | li(data-target='#products-carousel', data-slide-to='1')
9 | li(data-target='#products-carousel', data-slide-to='2')
10 | li(data-target='#products-carousel', data-slide-to='3')
11 | .carousel-inner(role='listbox')
12 | .item.active
13 | img(src='http://placehold.it/1440x600/2c3e50/ffffff?text=Banner+Image+One', alt='')
14 | .item
15 | img(src='http://placehold.it/1440x600/2c3e50/ffffff?text=Banner+Image+Two', alt='')
16 | .item
17 | img(src='http://placehold.it/1440x600/2c3e50/ffffff?text=Banner+Image+Three', alt='')
18 | .item
19 | img(src='http://placehold.it/1440x600/2c3e50/ffffff?text=Banner+Image+Four', alt='')
20 | a.left.carousel-control(href='#products-carousel', role='button', data-slide='prev')
21 | span.glyphicon.glyphicon-chevron-left(aria-hidden='true')
22 | span.sr-only Previous
23 | a.right.carousel-control(href='#products-carousel', role='button', data-slide='next')
24 | span.glyphicon.glyphicon-chevron-right(aria-hidden='true')
25 | span.sr-only Next
26 | .section
27 | .container
28 | .row
29 | .col-lg-4.col-md-4
30 | h3
31 | i.fa.fa-check-circle
32 | | Bootstrap 3 Built
33 | p
34 | | The 'Modern Business' website template by
35 | a(href='http://startbootstrap.com') Start Bootstrap
36 | | is built with
37 | a(href='http://getbootstrap.com') Bootstrap 3
38 | | . Make sure you're up to date with latest Bootstrap documentation!
39 | .col-lg-4.col-md-4
40 | h3
41 | i.fa.fa-pencil
42 | | Ready to Style & Edit
43 | p
44 | | You're ready to go with this pre-built page structure, now all you need to do is add your own custom stylings! You can see some free themes over at
45 | a(href='http://bootswatch.com') Bootswatch
46 | | , or come up with your own using
47 | a(href='http://getbootstrap.com/customize/') the Bootstrap customizer
48 | | !
49 | .col-lg-4.col-md-4
50 | h3
51 | i.fa.fa-folder-open
52 | | Many Page Options
53 | p
54 | | This template features many common pages that you might see on a business website. Pages include: about, contact, portfolio variations, blog, pricing, FAQ, 404, services, and general multi-purpose pages.
55 | .section-colored.text-center
56 | .container
57 | .row
58 | .col-lg-12
59 | h2 Modern Business: A Clean & Simple Full Website Template by Start Bootstrap
60 | p
61 | | A complete website design featuring various single page templates from Start Bootstraps library of free HTML starter templates.
62 | hr
63 | .section
64 | .container
65 | .row
66 | .col-lg-12.text-center
67 | h2 Display Some of Your Featured Products Here
68 | hr
69 | .col-lg-4.col-md-4.col-sm-6
70 | a(href='portfolio-item.html')
71 | img.img-responsive.ml-t(src='http://placehold.it/700x450/18bc9c/ffffff?text=Product+1')
72 | .col-lg-4.col-md-4.col-sm-6
73 | a(href='portfolio-item.html')
74 | img.img-responsive.ml-t(src='http://placehold.it/700x450/18bc9c/ffffff?text=Product+2')
75 | .col-lg-4.col-md-4.col-sm-6
76 | a(href='portfolio-item.html')
77 | img.img-responsive.ml-t(src='http://placehold.it/700x450/18bc9c/ffffff?text=Product+3')
78 | .section-colored
79 | .container
80 | .row
81 | .col-lg-6.col-md-6.col-sm-6
82 | h2 Usto odio dignissim qui blandit praesent:
83 | p
84 | | Consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem. Investigationes demonstraverunt lectores legere me lius quod ii legunt saepius. Claritas est etiam processus dynamicus, qui sequitur mutationem consuetudium lectorum. Mirum est notare quam littera gothica, quam nunc putamus parum claram, anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima. Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum. Lorem ipsum dolor sit amet.
85 | .col-lg-6.col-md-6.col-sm-6
86 | img.img-responsive(src='http://placehold.it/700x450/ffffff/cccccc?text=Featured+Image')
87 | .section
88 | .container
89 | .row
90 | .col-lg-6.col-md-6.col-sm-6
91 | img.img-responsive(src='http://placehold.it/700x450?text=Featured+Image')
92 | .col-lg-6.col-md-6.col-sm-6
93 | h2 Nibh euismod tincidunt ut laoreet:
94 | p
95 | | Sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem. Investigationes demonstraverunt lectores legere me lius quod ii legunt saepius. Claritas est etiam processus dynamicus, qui sequitur mutationem consuetudium lectorum. Mirum est notare quam littera gothica, quam nunc putamus parum claram, anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima. Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum. Lorem ipsum dolor sit amet. Consectetuer adipiscing elit
96 |
97 |
--------------------------------------------------------------------------------
/views/front/products-single.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | .container.products-view(class='item-#{item._id}')
5 | .col-md-8
6 | .panel.panel-default
7 | .panel-heading.item-name
8 | | #{item.name}
9 | .panel-body
10 | input.item-images-json(type='hidden', value='#{item_images}')
11 | .imageviewer
12 | .image-container
13 | span.glyphicon.glyphicon-chevron-left.prev
14 | span.glyphicon.glyphicon-chevron-right.next
15 | .footer-info
16 | span.current
17 | | /
18 | span.total
19 | hr
20 | h3 Product Description
21 | .item-content
22 | | !{item.content}
23 | .col-md-4
24 | .panel.panel-default
25 | .panel-heading
26 | | Product Details
27 | .panel-body
28 | h2.text-danger.m-0.ml-b
29 | | $
30 | span.item-price
31 | | #{parseFloat(item.price).toFixed(2)}
32 | small only
33 | .clearfix
34 | .col-md-6
35 | span.sr-only Four out of Five Stars
36 | span.glyphicon.glyphicon-star(aria-hidden='true')
37 | span.glyphicon.glyphicon-star(aria-hidden='true')
38 | span.glyphicon.glyphicon-star(aria-hidden='true')
39 | span.glyphicon.glyphicon-star(aria-hidden='true')
40 | span.glyphicon.glyphicon-star-empty(aria-hidden='true')
41 | span.label.label-success 7
42 | .col-md-6
43 | a.monospaced(href='#_')
44 | span.glyphicon.glyphicon-pencil
45 | | Write a Review
46 | hr
47 | p
48 | strong Date Posted
49 | | :
50 | span.item-date
51 | | #{item.date}
52 | p
53 | strong On Stock
54 | | :
55 | span.item-stock
56 | | #{item.stock}
57 | hr
58 | a.btn.btn-success.btn-block(href='#_')
59 | span.glyphicon.glyphicon-shopping-cart
60 | | Add to Cart
61 | a.btn.btn-primary.btn-block(href='#_')
62 | span.glyphicon.glyphicon-star
63 | | Add to Wishlist
64 | .panel.panel-default
65 | .panel-heading
66 | | Reviews
67 | .panel-body
68 |
--------------------------------------------------------------------------------
/views/front/products.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | .container.products-view-all
5 | form.post-list
6 | input(type='hidden', value='')
7 | .clearfix
8 | article.navbar-form.navbar-left.p-0.m-0.ml-b
9 | .form-group
10 | label Per Page:
11 | select.form-control.post_max.m-b
12 | option(value='4') 4
13 | option(value='8') 8
14 | option(value='16') 16
15 | label Search Keyword:
16 | input.form-control.post_search_text.m-b(type='text', placeholder='Enter a keyword')
17 | .form-group
18 | label Order By:
19 | select.form-control.post_name.m-b
20 | option(value='name') Title
21 | option(value='price') Price
22 | option(value='stock') On Stock
23 | select.form-control.post_sort.m-b
24 | option(value='ASC') ASC
25 | option(value='DESC') DESC
26 | input.btn.btn-primary.post_search_submit.m-b(type='submit', value='Filter')
27 | hr
28 |
29 | .clearfix
30 | .pagination-container.clearfix
31 | .pagination-nav
--------------------------------------------------------------------------------
/views/layout.jade:
--------------------------------------------------------------------------------
1 | include partials/header
2 |
3 | body
4 | block content
5 |
6 | include partials/footer
--------------------------------------------------------------------------------
/views/partials/footer.jade:
--------------------------------------------------------------------------------
1 | footer
2 | .container
3 | .row.well.ml-t
4 | .col-lg-8.col-md-8
5 | h4
6 | | Developed by:
7 | a.text-info.m-r(href='http://carlofontanos.com', target='_blank') Carl Victor C. Fontanos
8 | .personal-sm
9 | a.m-r(href='http://carlofontanos.com/about-me')
10 | i.fa.fa-home.text-success(title='')
11 | a.m-r(href='https://www.facebook.com/carlo.fontanos', target='_blank', title='Facebook')
12 | i.fa.fa-facebook.text-primary
13 | a.m-r(href='https://twitter.com/carlofontanos', target='_blank', title='Twitter')
14 | i.fa.fa-twitter.text-warning
15 | a.m-r(href='https://plus.google.com/u/0/107219338853998242780/about', target='_blank', title='GooglePlus')
16 | i.fa.fa-google-plus.text-danger
17 | a.m-r(href='https://ph.linkedin.com/in/carlfontanos', target='_blank', title='Linkedin')
18 | i.fa.fa-linkedin.text-success
19 | a.m-r(href='https://github.com/carlo-fontanos', target='_blank', title='Github')
20 | i.fa.fa-github.text-primary
21 | a(href='mailto:carl.fontanos@gmail.com', title='Contact Me')
22 | i.fa.fa-envelope-o.text-warning
23 | .col-lg-4.col-md-4.text-right
24 | p.m-t
25 | | Copyright ©
26 | a.text-info(href='http://carlofontanos.com', target='_blank') www.carlofontanos.com
27 | p All Rights Reserved.
28 |
29 | script(src='//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js')
30 | script(src='//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js')
31 | script(src='//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js')
32 | script(src='//cdn.ckeditor.com/4.5.10/standard/ckeditor.js')
33 | script(src='//cdnjs.cloudflare.com/ajax/libs/moment.js/2.9.0/moment-with-locales.js')
34 | script(src='//cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.42/js/bootstrap-datetimepicker.min.js')
35 | script(src='/libraries/lobibox/lobibox.min.js')
36 | script(src='/libraries/imageviewer/imageviewer.min.js')
37 | script(src='/javascripts/jquery.form.js')
38 | script(src='/socket.io/socket.io.js')
39 | script(src='/javascripts/global.js')
40 | script(src='/javascripts/app.js')
41 |
--------------------------------------------------------------------------------
/views/partials/header.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | head
4 | title= title
5 |
6 | link(rel='stylesheet', href='//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css')
7 | link(rel='stylesheet', href='//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
8 | link(rel='stylesheet', href='//cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.7/flatly/bootstrap.min.css')
9 | link(rel='stylesheet', href='/libraries/lobibox/lobibox.min.css')
10 | link(rel='stylesheet', href='/libraries/imageviewer/imageviewer.css')
11 | link(rel='stylesheet', href='/stylesheets/style.css')
12 |
13 | body.override
14 | nav.navbar.navbar-inverse(role="navigation")
15 | div.container
16 | div.navbar-header
17 | button(type="button", data-toggle="collapse", data-target="#navbar", aria-expanded="false").navbar-toggle.collapsed
18 | span.sr-only Toggle navigation
19 | span.icon-bar
20 | span.icon-bar
21 | span.icon-bar
22 | a(href="/").navbar-brand
23 | img.d-ib.ml-r(src="/images/logo.png", width="22")
24 | | Node.js E-commerce
25 |
26 | div#navbar.collapse.navbar-collapse
27 | ul.nav.navbar-nav.navbar-right
28 | li
29 | a(href="/products") Products
30 | if user
31 | li.dropdown
32 | a(href="#", data-toggle="dropdown", role="button", aria-haspopup="true", aria-expanded="false").dropdown-toggle Dashboard
33 | span.caret
34 | ul.dropdown-menu
35 | li
36 | a(href="/user/account") Account
37 | li
38 | a(href="/user/products") My Products
39 | li
40 | a(href="/user/products/add") Add Product
41 | if user
42 | li
43 | a(href="/user/logout") Logout
44 | else
45 | li
46 | a(href="/user/register") Register
47 | li
48 | a(href="/user/login") Login
--------------------------------------------------------------------------------
/views/user/account.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | .container.wave-box-wrapper
5 | .wave-box
6 | form.update-account(action='/user/account/update', method='post')
7 | .col-md-10.col-md-offset-1
8 | .panel.panel-default
9 | .panel-heading My Account
10 | .panel-body
11 | .form-group.clearfix
12 | label.col-md-4.control-label.text-right
13 | | Email
14 | span.text-red
15 | | :
16 | .col-md-6
17 | input.form-control(type='text', name='email', value='#{user.email}', disabled='')
18 |
19 | .form-group.clearfix
20 | label.col-md-4.control-label.text-right Old password:
21 | .col-md-6
22 | input.form-control(type='password', name='old_password', value='')
23 |
24 | .form-group.clearfix
25 | label.col-md-4.control-label.text-right New password:
26 | .col-md-6
27 | input.form-control(type='password', name='password', value='')
28 |
29 | .form-group.clearfix
30 | label.col-md-4.control-label.text-right Confirm New password:
31 | .col-md-6
32 | input.form-control(type='password', name='password_confirm', value='')
33 |
34 | .form-group.clearfix
35 | label.col-md-4.control-label.text-right First Name:
36 | .col-md-6
37 | input.form-control(type='first_name', name='first_name', value='#{user.first_name}')
38 |
39 | .form-group.clearfix
40 | label.col-md-4.control-label.text-right Last Name:
41 | .col-md-6
42 | input.form-control(type='last_name', name='last_name', value='#{user.last_name}')
43 |
44 | .form-group.clearfix
45 | label.col-md-4.control-label.text-right Phone Number:
46 | .col-md-6
47 | input.form-control(type='phone_number', name='phone_number', value='#{user.phone_number}')
48 |
49 | .form-group.clearfix
50 | label.col-md-4.control-label.text-right About Me:
51 | .col-md-6
52 | textarea#ck-editor-area.form-control(name='about_me') #{user.about_me}
53 |
54 | .col-md-6.col-md-offset-4
55 | input.btn.btn-success(type='submit', value='Update')
--------------------------------------------------------------------------------
/views/user/login.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | .container
5 | form(method='post', action='/user/login')
6 | .col-md-8.col-md-offset-2
7 | .panel.panel-default
8 | .panel-heading Login
9 | .panel-body
10 | if message != ''
11 | p.bg-danger.p-d #{message}
12 | .form-group.clearfix
13 | label.col-md-4.control-label.text-right(for='email') Email:
14 | .col-md-6
15 | input.form-control(name='email', value="", type='text')
16 | .form-group.clearfix
17 | label.col-md-4.control-label.text-right(for='password') Password:
18 | .col-md-6
19 | input.form-control(name='password', type='password')
20 | .col-md-6.col-md-offset-4
21 | input.btn.btn-success(name='login', type='submit', value='Login')
--------------------------------------------------------------------------------
/views/user/products-add.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | .container
5 | p.ml-b
6 | a.ml-b.text-success(href='/user/products')
7 | span.glyphicon.glyphicon-chevron-left
8 | | All products
9 | .panel.panel-default
10 | .panel-heading
11 | | Add Product
12 | .panel-body
13 | form.create-product(method='post', action='/user/products/create')
14 | .col-md-8
15 | .form-group
16 | label Name*
17 | input.form-control.required(type='text', name='name', value='')
18 | .form-group
19 | label Description
20 | textarea#ck-editor-area.form-control(name='content')
21 | .form-group
22 | label Short Description
23 | textarea.form-control(name='excerpt', rows='7')
24 | .col-md-4
25 | .form-group
26 | label Price*
27 | .input-group
28 | .input-group-addon $
29 | input.form-control.required(type='text', name='price', value='', placeholder='Amount')
30 | .form-group
31 | label Status
32 | select.form-control(name='status')
33 | option(value='1') Active
34 | option(value='0') Inactive
35 | .form-group
36 | label Date
37 | .input-group.date.datepicker
38 | input.form-control(type='text', name='date', value='')
39 | span.input-group-addon
40 | span.glyphicon.glyphicon-calendar
41 | .form-group
42 | label On Stock*
43 | input.form-control.required(type='number', name='stock', value='')
44 | input.btn.btn-success(type='submit', value='Submit')
45 |
--------------------------------------------------------------------------------
/views/user/products-edit.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | .container.item-edit(id='item-#{item._id}')
5 | p.ml-b
6 | a.ml-b.text-success(href='/user/products')
7 | span.glyphicon.glyphicon-chevron-left
8 | | All products
9 | .panel.panel-default
10 | .panel-heading
11 | | Add Product
12 | .panel-body
13 | form.update-product(method='post', action='/user/products/update', enctype='multipart/form-data')
14 | input(type='hidden', name='id' value='#{item._id}')
15 | .col-md-8
16 | .form-group
17 | label Name*
18 | input.form-control.required(type='text', name='name', value='#{item.name}')
19 | .form-group
20 | label Description
21 | textarea#ck-editor-area.form-control(name='content') #{item.content}
22 | .form-group
23 | label Short Description
24 | textarea.form-control(name='excerpt', rows='7') #{item.excerpt}
25 | .form-group.ml-t
26 | label Upload Images
27 | input.form-control.image-input(type='file', name='images', accept='image/*', multiple='')
28 | hr
29 | .clearfix.m-t.images-section
30 | - if (item.images){
31 | - var image_count = 1;
32 | each image, i in item.images
33 | .col-sm-3
34 | span.unset-image.glyphicon.glyphicon-remove.text-danger.lead.m-0.c-p(id='unset-#{image}', title='Delete image')
35 | - if (item.featured_image == image){
36 | span.set-featured-image.glyphicon.glyphicon-star.lead.m-0.c-p(title='Set as featured image', id='featured-#{image}', style='color: #E4C317')
37 | - } else {
38 | span.set-featured-image.glyphicon.glyphicon-star-empty.lead.m-0.c-p(title='Set as featured image', id='featured-#{image}')
39 | - }
40 | img.img-thumbnail.img-responsive(src='/images/uploads/#{image}')
41 | - if( image_count%4 == 0 ){
42 | .clearfix
43 | - }
44 | - image_count++;
45 | - } else {
46 | p.alert.alert-danger.no-item-images No images found
47 | - }
48 | .col-md-4
49 | .form-group
50 | label Price*
51 | .input-group
52 | .input-group-addon $
53 | input.form-control.required(type='text', name='price', value='#{item.price}', placeholder='Amount')
54 | .form-group
55 | label Status
56 | - var select=null; if (item.status == 0) select='selected';
57 | select.form-control(name='status')
58 | option(value='1') Active
59 | option(value='0', selected=select) Inactive
60 | .form-group
61 | label Date
62 | .input-group.date.datepicker
63 | input.form-control(type='text', name='date', value='#{item.date}')
64 | span.input-group-addon
65 | span.glyphicon.glyphicon-calendar
66 | .form-group
67 | label On Stock*
68 | input.form-control.required(type='number', name='stock', value='#{item.stock}')
69 | input.btn.btn-success(type='submit', value='Update')
70 |
--------------------------------------------------------------------------------
/views/user/products.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | .container.products-view-user
5 | form.post-list
6 | input(type='hidden', value='')
7 | article.navbar-form.navbar-left.p-0.m-0.ml-b
8 | .form-group
9 | label Per Page:
10 | select.form-control.post_max
11 | option(value='4') 4
12 | option(value='8') 8
13 | option(value='16') 16
14 | label Search Keyword:
15 | input.form-control.post_search_text(type='text', placeholder='Enter a keyword')
16 | input.btn.btn-primary.post_search_submit(type='submit', value='Filter')
17 |
18 | a.btn.btn-success.pull-right(href='/user/products/add') Add New
19 | br.clear
20 |
21 | .wave-box-wrapper
22 | .wave-box
23 | table.table.table-striped.table-post-list.no-margin
24 | thead
25 | tr
26 | th Image
27 | th#name.active
28 | a(href='#') Name
29 | th#price
30 | a(href='#') Price
31 | th#status
32 | a(href='#') Status
33 | th#date
34 | a(href='#') Date
35 | th#stock
36 | a(href='#') On Stock
37 | th Action
38 | tbody.pagination-container
39 | .pagination-nav
--------------------------------------------------------------------------------
/views/user/register.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | .container.wave-box-wrapper
5 | .wave-box
6 | form.create-account(action="/user/register/create", method="post")
7 | .col-md-8.col-md-offset-2
8 | .panel.panel-default
9 | .panel-heading Register
10 | .panel-body
11 | .form-group.clearfix
12 | label.col-md-4.control-label.text-right
13 | | Email
14 | span.text-red *
15 | | :
16 | .col-md-6
17 | input.form-control(type="text", name="email", value="")
18 | .form-group.clearfix
19 | label.col-md-4.control-label.text-right
20 | | Password
21 | span.text-red *
22 | | :
23 | .col-md-6
24 | input.form-control(type="password", name="password", value="")
25 | .form-group.clearfix
26 | label.col-md-4.control-label.text-right Confirm password:
27 | .col-md-6
28 | input.form-control(type="password", name="password2", value="")
29 | .form-group.clearfix
30 | label.col-md-4.control-label.text-right First Name:
31 | .col-md-6
32 | input.form-control(type="first_name", name="first_name", value="")
33 | .form-group.clearfix
34 | label.col-md-4.control-label.text-right Last Name:
35 | .col-md-6
36 | input.form-control(type="last_name", name="last_name", value="")
37 | .form-group.clearfix
38 | label.col-md-4.control-label.text-right Phone Number:
39 | .col-md-6
40 | input.form-control(type="phone_number", name="phone_number", value="")
41 | .col-md-6.col-md-offset-4
42 | input.btn.btn-success(type="submit", value="Register")
43 |
--------------------------------------------------------------------------------