diff options
28 files changed, 604 insertions, 82 deletions
diff --git a/Gruntfile.js b/Gruntfile.js index 21bbfb0..ed236c5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -99,6 +99,7 @@ module.exports = function(grunt) { "public/assets/javascripts/ui/site/LayoutsModal.js", "public/assets/javascripts/ui/site/EditProjectModal.js", "public/assets/javascripts/ui/site/EditProfileModal.js", + "public/assets/javascripts/ui/site/EditSubscriptionModal.js", "public/assets/javascripts/ui/site/DocumentModal.js", "public/assets/javascripts/ui/site/HomeView.js", @@ -140,7 +141,7 @@ module.exports = function(grunt) { }, uglify: { options: { - banner: '/* vvalls by okfocus 2014 */\n' + banner: '/* vvalls by okfocus 2015 */\n' }, js: { src: 'public/assets/javascripts/app.concat.js', @@ -1 +1,2 @@ -web: node server
\ No newline at end of file +web: node server +webhook: node server/lib/webhook
\ No newline at end of file diff --git a/config.json.example b/config.json.example index 6028021..dc79edd 100644 --- a/config.json.example +++ b/config.json.example @@ -3,7 +3,7 @@ "hostName": "lvh.me", "port": 3000, "socketPort": 1337, + "webhookPort": 5000, "databaseHost": "lvh.me", - "pageSize": 10, "env": { "development": 1 } } diff --git a/package.json b/package.json index adefb82..8afb96e 100644 --- a/package.json +++ b/package.json @@ -6,32 +6,34 @@ "url": "git://github.com/okfocus/vvalls.git" }, "dependencies": { - "express": "~3.4.8", - "monk": "~0.7.1", - "socket.io": "~0.9.16", + "body-parser": "1.3.0", "connect-mongo": "~0.4.1", - "passport": "~0.2.0", - "passport-local": "~1.0.0", - "passport-twitter": "~1.0.2", - "passport-facebook": "~1.0.3", - "passport.socketio": "~3.0.1", - "node-restful": "~0.1.14", "ejs": "^0.8.8", - "useful-string": "0.0.1", + "emailjs": "~0.3.6", + "express": "~3.4.8", "express-subdomain-handler": "~0.1.0", "express-subdomains": "0.0.5", + "html-entities": "~1.0.10", + "intro.js": "^0.9.0", + "knox": "~0.8.10", "lodash": "~2.4.1", + "marked": "~0.3.2", + "moment": "~2.6.0", "mongoose": "~3.8.8", - "mongoose-unique-validator": "~0.3.0", "mongoose-lifecycle": "~1.0.0", - "knox": "~0.8.10", - "moment": "~2.6.0", - "html-entities": "~1.0.10", + "mongoose-unique-validator": "~0.3.0", + "monk": "~0.7.1", "multer": "~0.1.0", - "body-parser": "1.3.0", - "marked": "~0.3.2", - "emailjs": "~0.3.6", - "intro.js": "^0.9.0" + "node-recurly": "^2.1.0", + "node-restful": "~0.1.14", + "passport": "~0.2.0", + "passport-facebook": "~1.0.3", + "passport-local": "~1.0.0", + "passport-twitter": "~1.0.2", + "passport.socketio": "~3.0.1", + "socket.io": "~0.9.16", + "useful-string": "0.0.1", + "xml2js": "^0.4.4" }, "devDependencies": { "grunt": "~0.4.1", diff --git a/public/assets/javascripts/app.js b/public/assets/javascripts/app.js index 41edafe..a146325 100644 --- a/public/assets/javascripts/app.js +++ b/public/assets/javascripts/app.js @@ -3,7 +3,7 @@ if (is_mobile) { $("html").addClass("mobile") } else { - $("html").addClass("desktop") + $("html").addClass("desktop") } @@ -23,6 +23,8 @@ app.launch = function () { var movements + app.devicePixelRatio = is_mobile ? devicePixelRatio : 1 + scene = new MX.Scene().addTo('#scene') scene.width = window.innerWidth scene.height = window.innerHeight diff --git a/public/assets/javascripts/mx/mx.js b/public/assets/javascripts/mx/mx.js index d59a551..ab9a9a0 100644 --- a/public/assets/javascripts/mx/mx.js +++ b/public/assets/javascripts/mx/mx.js @@ -162,24 +162,24 @@ var MX = MX || (function (undefined) { Object.defineProperty(this, 'width', { get: function () { return width - || parseInt(self.el.style.width*devicePixelRatio, 10) + || parseInt(self.el.style.width, 10) * app.devicePixelRatio || 0 }, set: function (val) { width = val - this.el.style.width = (width/devicePixelRatio) + 'px' + this.el.style.width = (width/app.devicePixelRatio) + 'px' } }) Object.defineProperty(this, 'height', { get: function () { return height - || parseInt(self.el.style.height*devicePixelRatio, 10) + || parseInt(self.el.style.height, 10) * app.devicePixelRatio || 0 }, set: function (val) { height = val - this.el.style.height = (height/devicePixelRatio) + 'px' + this.el.style.height = (height/app.devicePixelRatio) + 'px' } }) } @@ -302,9 +302,9 @@ var MX = MX || (function (undefined) { + (-this.y).toFixed(floatPrecision) + 'px,' + (-this.z).toFixed(floatPrecision) + 'px) ' + 'scale3d(' - + (devicePixelRatio * this.scaleX).toFixed(floatPrecision) + ',' - + (devicePixelRatio * this.scaleY).toFixed(floatPrecision) + ',' - + (devicePixelRatio * this.scaleZ).toFixed(floatPrecision) + ') ' + + (app.devicePixelRatio * this.scaleX).toFixed(floatPrecision) + ',' + + (app.devicePixelRatio * this.scaleY).toFixed(floatPrecision) + ',' + + (app.devicePixelRatio * this.scaleZ).toFixed(floatPrecision) + ') ' if (rotationTranslation) { transformString += rotationTranslation.before diff --git a/public/assets/javascripts/ui/_router.js b/public/assets/javascripts/ui/_router.js index 3532428..9e7ce75 100644 --- a/public/assets/javascripts/ui/_router.js +++ b/public/assets/javascripts/ui/_router.js @@ -9,6 +9,7 @@ var SiteRouter = Router.extend({ "click [data-role='new-project-modal']": 'newProject', "click [data-role='edit-project-modal']": 'editProject', "click [data-role='edit-profile-modal']": 'editProfile', + "click [data-role='edit-subscription-modal']": 'editSubscription', "click [data-role='new-document-modal']": 'newDocument', "click [data-role='edit-document-modal']": 'editDocument', "click [data-role='destroy-document-modal']": 'destroyDocument', @@ -29,6 +30,7 @@ var SiteRouter = Router.extend({ "/profile": 'profile', "/profile/edit": 'editProfile', + "/profile/billing": 'editSubscription', "/profile/:name": 'profile', "/about/:name/edit": 'editDocument', "/about/new": 'newDocument', @@ -56,6 +58,7 @@ var SiteRouter = Router.extend({ "/profile": 'profile', "/profile/edit": 'editProfile', + "/profile/billing": 'editSubscription', "/profile/:name": 'profile', "/project/:name": 'projectViewer', @@ -69,6 +72,7 @@ var SiteRouter = Router.extend({ this.newProjectModal = new NewProjectModal() this.editProjectModal = new EditProjectModal() this.editProfileModal = new EditProfileModal() + this.editSubscriptionModal = new EditSubscriptionModal() this.passwordForgotModal = new PasswordForgot() this.documentModal = new DocumentModal() this.profileView = new ProfileView() @@ -195,6 +199,12 @@ var SiteRouter = Router.extend({ this.editProfileModal.load() }, + editSubscription: function(e){ + e && e.preventDefault() + window.history.pushState(null, document.title, "/profile/billing") + + this.editSubscriptionModal.load() + }, newDocument: function(e){ diff --git a/public/assets/javascripts/ui/site/EditSubscriptionModal.js b/public/assets/javascripts/ui/site/EditSubscriptionModal.js new file mode 100644 index 0000000..1b3b859 --- /dev/null +++ b/public/assets/javascripts/ui/site/EditSubscriptionModal.js @@ -0,0 +1,21 @@ + +var EditSubscriptionModal = ModalFormView.extend({ + el: ".mediaDrawer.editSubscription", + action: "/api/subscription", + method: "put", + + fixedClose: true, + + events: { + "click [data-role='changePasswordToggle']": 'togglePasswordFields' + }, + + load: function(){ + this.reset() + $.get("/api/subscription", function(data){ + + this.show() + }.bind(this)) + }, + +}) diff --git a/public/assets/stylesheets/app.css b/public/assets/stylesheets/app.css index a149166..0ce2c5e 100755 --- a/public/assets/stylesheets/app.css +++ b/public/assets/stylesheets/app.css @@ -929,6 +929,9 @@ border-left: 1px solid black; .no-templates { display: none; } +.no-templates a { + border-bottom: 1px solid; +} .templates span { display: block; @@ -2373,6 +2376,7 @@ button { font-weight: 500; width: 100%; font-size:14px; + font-family:'Lato', sans-serif; } #builder-units { diff --git a/server/index.js b/server/index.js index 9a9323c..475054d 100644 --- a/server/index.js +++ b/server/index.js @@ -72,7 +72,7 @@ site.setup = function(){ server = http.createServer(app) server.listen(app.get('port'), function () { - console.log('Express server listening on port ' + app.get('port')); + console.log('Vvalls server listening on port ' + app.get('port')); }); // var io = websocket.listen(server) @@ -102,6 +102,7 @@ site.route = function () { app.get('/profile', views.profile) app.get('/profile/edit', views.profile) + app.get('/profile/billing', views.profile) app.get('/profile/:username', views.profile) app.get('/about', views.docs); diff --git a/server/lib/api/index.js b/server/lib/api/index.js index 11e13fc..9478d9b 100644 --- a/server/lib/api/index.js +++ b/server/lib/api/index.js @@ -8,6 +8,7 @@ var api = { projects: require('./projects'), rooms: require('./rooms'), collaborator: require('./collaborator'), + subscription: require('./subscription'), } module.exports = api diff --git a/server/lib/api/profile.js b/server/lib/api/profile.js index 996505f..d72a2c3 100644 --- a/server/lib/api/profile.js +++ b/server/lib/api/profile.js @@ -32,6 +32,7 @@ var profile = { delete data.old_password delete data.new_password delete data.isStaff + delete data.plan_level data.updated_at = new Date () if (req.files.avatar) { diff --git a/server/lib/api/subscription.js b/server/lib/api/subscription.js new file mode 100644 index 0000000..83644cf --- /dev/null +++ b/server/lib/api/subscription.js @@ -0,0 +1,47 @@ +/* jshint node: true */ + +var _ = require('lodash'), + util = require('../util'), + upload = require('../upload'), + config = require('../../../config.json'), + User = require('../schemas/User'), + Project = require('../schemas/Project'), + Layout = require('../schemas/Layout'), + Subscription = require('../schemas/Subscription'); + +var subscription = module.exports = { + +/* + index: function(req, res){ + Project.find({ user_id: req.user._id }, function(err, docs){ + res.json(docs) + }) + }, +*/ + middleware: { + fetchAccount: function(req, res, next){ + recurly.subscriptions.listByAccount(req.user._id, function(data){ + }) + }, + }, + + // synchronise an account with recurly.. + // useful when testing locally (if webhooks do not fire) + sync: function(req, res){ + // fetch req.user._id + }, + + show: function(req, res){ + // fetch from recurly + }, + + update: function(req, res){ + // update plan_type on recurly + // update add_ons on recurly + }, + + destroy: function(req, res){ + // destroy on recurly + }, + +};
\ No newline at end of file diff --git a/server/lib/schemas/Plan.js b/server/lib/schemas/Plan.js index 1208672..8a19b99 100644 --- a/server/lib/schemas/Plan.js +++ b/server/lib/schemas/Plan.js @@ -10,6 +10,8 @@ var PlanSchema = new mongoose.Schema({ name: { type: String }, slug: { type: String }, + level: { type: Number }, + monthly_price: { type: Number }, yearly_price: { type: Number }, diff --git a/server/lib/schemas/Subscription.js b/server/lib/schemas/Subscription.js index 8ec557d..b766555 100644 --- a/server/lib/schemas/Subscription.js +++ b/server/lib/schemas/Subscription.js @@ -8,13 +8,14 @@ var mongoose = require('mongoose'), var SubscriptionSchema = new mongoose.Schema({ user_id: { type: mongoose.Schema.ObjectId, index: true }, - - monthly_total: { type: Number }, - yearly_total: { type: Number }, - - plans: [{ - tier: { type: String }, - monthly: { type: Boolean }, + + plan_type: { type: String, default: "free" }, + plan_period: { type: String, default: "monthly" }, + + subscription_uuid: { type: String }, + subscription_add_ons: [{ + name: { type: String }, + quantity: { type: Number }, }], history: [{ diff --git a/server/lib/schemas/User.js b/server/lib/schemas/User.js index 19b5ede..4b6ff39 100644 --- a/server/lib/schemas/User.js +++ b/server/lib/schemas/User.js @@ -54,7 +54,11 @@ var UserSchema = new mongoose.Schema({ type: String, default: "", }, - + + plan_level: { type: Number, default: 0 }, + plan_type: { type: String, default: "free" }, + last_charged: { type: Date, default: null }, + location: { type: String, default: "" }, photo: { type: String, default: "" }, bio: { type: String, default: "" }, diff --git a/server/lib/views/index.js b/server/lib/views/index.js index 2a8f921..0ce0357 100644 --- a/server/lib/views/index.js +++ b/server/lib/views/index.js @@ -22,7 +22,6 @@ marked.setOptions({ var views = module.exports = { staff: require('./staff'), - subscription: require('./subscription'), editor_new: function (req, res) { if (! req.user) { diff --git a/server/lib/views/staff.js b/server/lib/views/staff.js index 97ecde6..74dd7cd 100644 --- a/server/lib/views/staff.js +++ b/server/lib/views/staff.js @@ -124,7 +124,35 @@ var staff = module.exports = { next() }) }, - + + ensureSubscriptions: function(req, res, next){ + var paginationInfo = res.locals.pagination = {} + var criteria = req.criteria || {} + var limit = paginationInfo.limit = Math.min( Number(req.query.limit) || 50, 200 ) + var offset = paginationInfo.offset = Number(req.query.offset) || 0 + var sort + paginationInfo.sort = req.query.sort + paginationInfo.sortOptions = ["date", "name"] + switch (req.query.sort) { + case 'created': + sort = {'created_at': -1} + break + default: + case 'date': + sort = {'updated_at': -1} + break + } + Subscription.find(criteria) + .select(staff.fields.project) + .sort(sort) + .skip(offset) + .limit(limit) + .exec(function (err, subscriptions) { + res.locals.subscriptions = subscriptions.map(staff.helpers.subscription) + next() + }) + }, + ensurePlans: function(req, res, next){ Plan.find(function (err, plans) { res.locals.plans = (plans || []).map(staff.helpers.plan) @@ -150,6 +178,24 @@ var staff = module.exports = { } }, + ensureSubscription: function (req, res, next) { + if (req.params.id) { + Subscription.findOne({ _id: req.params.id }, function(err, subscription){ + if (err || ! subscription) { + console.error(err) + res.redirect("/staff/subscriptions/") + } + else { + req.subscription = subscription + next() + } + }) + } + else { + res.redirect("/staff/subscriptions/") + } + }, + ensureRecentProjects: function(req, res, next){ var dreq = { params: { sort: 'created_at', limit: 20, offset: 0 } } staff.middleware.ensureProjects(dreq, res, next) @@ -160,11 +206,23 @@ var staff = module.exports = { staff.middleware.ensureObjectsUsers(res.locals.projects, next) }, + ensureSubscriptionsUsers: function(req, res, next){ + if (! res.locals.subscriptions || ! res.locals.subscriptions.length) { return next() } + staff.middleware.ensureObjectsUsers(res.locals.subscriptions, next) + }, + ensureMediaUsers: function(req, res, next){ if (! res.locals.media || ! res.locals.media.length) { return next() } staff.middleware.ensureObjectsUsers(res.locals.media, next) }, + ensureSubscriptionUser: function(req, res, next){ + if (! res.locals.subscription) { return next() } + staff.middleware.ensureObjectsUsers([ res.locals.subscription ], function(){ + next() + }) + }, + ensureMediaUser: function(req, res, next){ if (! res.locals.media) { return next() } staff.middleware.ensureObjectsUsers([ res.locals.media ], function(){ @@ -338,6 +396,13 @@ var staff = module.exports = { plan.user = {} return plan }, + + subscription: function(subscription){ + subscription = subscription.toObject() + subscription.date = moment( subscription.updated_at || subscription.created_at ).format("M/DD/YYYY hh:mm a") + subscription.user = {} + return subscription + }, }, route: function(app){ @@ -500,6 +565,27 @@ var staff = module.exports = { staff.plans.update ); + + // + // subscriptions + app.get('/staff/subscriptions', + middleware.ensureAuthenticated, + middleware.ensureIsStaff, + + staff.middleware.ensureSubscriptions, + staff.middleware.ensureSubscriptionsUsers, + + staff.subscriptions.index + ); + app.get('/staff/subscriptions/:id', + middleware.ensureAuthenticated, + middleware.ensureIsStaff, + + staff.middleware.ensureSubscription, + staff.middleware.ensureSubscriptionUser, + + staff.subscriptions.show + ); }, paginate: function(req, res){ @@ -669,6 +755,18 @@ var staff = module.exports = { res.redirect("/staff/plans/") }) }, - } + }, + + subscriptions: { + index: function(req, res){ + res.locals.pagination.count = res.locals.subscriptions.length + res.locals.pagination.max = res.locals.subscriptionCount + staff.paginate(req, res) + res.render('staff/subscriptions/index') + }, + show: function(req, res){ + res.render('staff/subscriptions/show') + }, + }, } diff --git a/server/lib/webhook/index.js b/server/lib/webhook/index.js new file mode 100644 index 0000000..11419c2 --- /dev/null +++ b/server/lib/webhook/index.js @@ -0,0 +1,44 @@ +var config = require('../../../config.json'), + http = require('http'), + express = require('express'), + bodyParser = require('body-parser'), + mongoose = require('mongoose'); + +var http = require('http'), + express = require('express'), + bodyParser = require('body-parser'), + multer = require('multer'), + MongoStore = require('connect-mongo')(express), + passport = require('passport'), + path = require('path'), + mongoose = require('mongoose'); + +var webhook = require('./webhook'); + +var app = express() +var server +var DATABASE_URI = process.env.MONGOLAB_URI || ('mongodb://' + config.databaseHost + '/vvalls') + +var site = {} + +site.init = function(){ + mongoose.connect(DATABASE_URI, {}, site.ready); +} + +site.ready = function(){ + app.set('port', config.webhookPort); + app.use(bodyParser()); + app.use(express.query()); + app.set('env', config.env.production ? "production" : "development") + app.get('env') === 'development' && app.use(express.errorHandler()); + + server = http.createServer(app) + server.listen(app.get('port'), function () { + console.log('Webhook server listening on port ' + app.get('port')); + }); + + app.get('/', function(req,res){ res.send('HI THERE') }) + webhook.route(app) +} + +site.init() diff --git a/server/lib/webhook/recurly-config.js b/server/lib/webhook/recurly-config.js new file mode 100644 index 0000000..3d7e1c5 --- /dev/null +++ b/server/lib/webhook/recurly-config.js @@ -0,0 +1,6 @@ +module.exports = { + API_KEY: process.env['VVALLS_RECURLY_SECRET'], + SUBDOMAIN: 'vvalls', + ENVIRONMENT: 'sandbox', + DEBUG: true, +}; diff --git a/server/lib/webhook/webhook.js b/server/lib/webhook/webhook.js new file mode 100644 index 0000000..4f23d0b --- /dev/null +++ b/server/lib/webhook/webhook.js @@ -0,0 +1,154 @@ +// // where should this live? +// app.get('/subscribe/webhook', views.subscription.webhook); + +/* +app.use(express.basicAuth(function(user, pass, callback) { + var result = (user === 'testUser' && pass === 'testPass'); + callback(null, result); +})); +*/ + + +/* jshint node: true */ + +var User = require('../schemas/User'), + Subscription = require('../schemas/Subscription'), + config = require('../../../config'), + middleware = require('../middleware'), + util = require('../util'), + _ = require('lodash'), + moment = require('moment'), + xml2js = require('xml2js'), + Recurly = require('node-recurly'), + recurly = new Recurly(require('./recurly-config')); + +var parser = new xml2js.Parser(); + +var subscribe = module.exports = { + plan_level: { + free: 0, + basic: 1, + pro: 2, + }, + + callbacks: { +/* + // accounts + new_account_notification: function(data, user){ + // fires on successful signup + }, + canceled_account_notification: function(data, user){ + }, + billing_info_updated_notification: function(data, user){ + }, + reactivated_account_notification: function(data, user){ + }, + + // invoices + new_invoice_notification: function(data, user){ + }, + closed_invoice_notification: function(data, user){ + }, + past_due_invoice_notification: function(data, user){ + }, +*/ + + // subscriptions + new_subscription_notification: function(data, user){ + var account = data.account + Subscription.findOne({ "uuid": data.subscription.uuid }, function(err, subscription){ + if (err || subscription) return; + + var plan = data.subscription.plan.plan_code.split("-") + var plan_type = plan[0] + var plan_period = plan[1] + + user.plan_type = plan_type + user.plan_level = subscribe.plan_level[plan_type] + + var subscriber = new Subscription () + subscriber.uuid = data.subscription.uuid + subscriber.user_id = user._id + subscriber.plan_type = plan_type + subscriber.plan_period = plan_period + subscriber.plan_level = subscribe.plan_level[plan_type] + subscriber.add_ons = subscription.add_ons.map(function(add_on){ + return { + name: add_on.plan, + quantity: add_on.quantity, + } + }) + subscriber.save(function(err, data){ + if (err) return; + user.save(function(err){ + // saved! + }) + }) + }) + }, + +/* + updated_subscription_notification: function(data, user){ + }, + canceled_subscription_notification: function(data, user){ + }, + expired_subscription_notification: function(data, user){ + }, + renewed_subscription_notification: function(data, user){ + }, +*/ + // payments + successful_payment_notification: function(data, user){ + var account = data.account + user.last_charged = new Date(data.transaction.date) + user.save(function(){ + }) + }, +/* + failed_payment_notification: function(data, user){ + }, + successful_refund_notification: function(data, user){ + }, + void_payment_notification: function(data, user){ + }, +*/ + }, + + execute: function(action, data){ + User.findOne({ _id: data.account.account_code }, function(err, user){ + if (err) { return } + subscribe.callbacks[action](data, user) + }) + }, + + // then calls to get appropriate info from the recurly api + handle: function(req, res){ + res.status(200).end() + parser.parseString(data, function (err, result) { + console.log(inspect(result, { colors: true, depth: Infinity })); + for (var action in result) { + if (subscribe.callbacks[action]) { + subscribe.execute(action, result[action]); + } + } + }); + }, + + list: function(req, res){ + recurly.subscriptions.listByAccount(req.params.id, function(data){ + if (data.data != 404) { + res.json(data) + return + } + else { + res.json(data) + return + } + }) + }, + + route: function(app){ + app.post('/subscribe/webhook', subscribe.handle); + app.get('/subscribe/list/:id', subscribe.list); + }, +} diff --git a/views/about/brochure.ejs b/views/about/brochure.ejs index e75f8c6..49b03db 100644 --- a/views/about/brochure.ejs +++ b/views/about/brochure.ejs @@ -33,8 +33,14 @@ <li> Each new floor plan can have up to [[- plans.basic.basic_project_limit ]] exhibitions <li> VValls logo appears when embedding an exhibition on a web page <li> - <!-- check current subscription plan --> - <button>Buy Now</button> + [[ if (! logged_in) { ]] + <button href="/signup">Sign Up</button> + [[ } else if (! user.plan_level || user.plan_level < plan.level) { ]] + <button href="https://vvalls.recurly.com/subscribe/basic/[[- user._id ]]/[[- user.username ]]">Buy Now</button> + [[ } else if (user.plan_level == plan.level) { ]] + Current Level + [[ } else { ]] + [[ } ]] </ul> </div> @@ -49,8 +55,13 @@ <li> Includes planning for 3D objects in the room <li> No VValls logo on embed <li> - <!-- check current subscription plan --> - <button>Buy Now</button> + [[ if (! logged_in) { ]] + <button href="/signup">Sign Up</button> + [[ } else if (! user.plan_level || user.plan_level < plan.level) { ]] + <button href="https://vvalls.recurly.com/subscribe/pro/[[- user._id ]]/[[- user.username ]]">Buy Now</button> + [[ } else if (user.plan_level == plan.level) { ]] + Current Level + [[ } ]] </ul> </div> @@ -112,6 +123,9 @@ text-align: center; margin-bottom: 10px; } +.about_plan ul { + margin-bottom: 60px; +} .planbox li { list-style-type: none; margin-bottom: 5px; diff --git a/views/partials/edit-subscription.ejs b/views/partials/edit-subscription.ejs new file mode 100644 index 0000000..adc3f71 --- /dev/null +++ b/views/partials/edit-subscription.ejs @@ -0,0 +1,51 @@ +<div class="mediaDrawer fixed animate editSubscription"> + <span class="close">X</span> + <div id="form_container"> + <form enctype="multipart/form-data" method="post"> + <input type="hidden" name="_csrf" value="[[- token ]]"> + <ul> + <li class="section_break"> + <h3>Edit Subscription</h3> + </li> + <div id="free_plan"> + You are currently using the free plan. For access to all of Vvalls features, + consider upgrading to a paid plan. + <p> + <a href="/about/brochure">View the Plans</a> + </div> + <div id="free_plan"> + Your current plan level is <span id="user_plan_type"></span> + + <table> + <tr> + <td>Basic plan</td> + <td></td> + <td>@ $<span></span>/<span></span></td> + </tr> + <tr> + <td>Additional basic layouts</td> + <td></td> + <td>@ $<span></span>/<span></span></td> + <td>Buy more</td> + </tr> + <tr> + <td>Additional PRO layouts</td> + <td></td> + <td>$<span></span>/<span></span></td> + <td>Buy more</td> + </tr> + <tr> + <td>Total</td> + <td></td> + <td>$<span></span>/<span></span></td> + </tr> + </table> + + <button>Upgrade your subscription</button> + + <button>Cancel your subscription</button> + </li> + </ul> + </form> + </div> +</div> diff --git a/views/partials/scripts.ejs b/views/partials/scripts.ejs index fc94992..04bd945 100644 --- a/views/partials/scripts.ejs +++ b/views/partials/scripts.ejs @@ -95,6 +95,7 @@ <script type="text/javascript" src="/assets/javascripts/ui/site/LayoutsModal.js"></script> <script type="text/javascript" src="/assets/javascripts/ui/site/EditProjectModal.js"></script> <script type="text/javascript" src="/assets/javascripts/ui/site/EditProfileModal.js"></script> +<script type="text/javascript" src="/assets/javascripts/ui/site/EditSubscriptionModal.js"></script> <script type="text/javascript" src="/assets/javascripts/ui/site/DocumentModal.js"></script> <script type="text/javascript" src="/assets/javascripts/ui/site/HomeView.js"></script> diff --git a/views/profile.ejs b/views/profile.ejs index 88af6b0..e149847 100644 --- a/views/profile.ejs +++ b/views/profile.ejs @@ -9,48 +9,53 @@ [[- include partials/header ]] <div class="profilepage"> - [[ if (profile.photo && profile.photo.length) { ]] - <div class="profilePic" style="background-image:url([[- profile.photo ]])"> - </div> - [[ } else { ]] - <div class="profilePic noPic"> - <span class="holder"> - <span class="ion-ios7-person-outline"></span> - [[ if (isOwnProfile) { ]] - <div>click to add profile pic</div> - <input id="profile_avatar" name="avatar" class="element file" type="file"> - [[ } ]] - </span> - </div> + [[ if (profile.photo && profile.photo.length) { ]] + <div class="profilePic" style="background-image:url([[- profile.photo ]])"> + </div> + [[ } else { ]] + <div class="profilePic noPic"> + <span class="holder"> + <span class="ion-ios7-person-outline"></span> + [[ if (isOwnProfile) { ]] + <div>click to add profile pic</div> + <input id="profile_avatar" name="avatar" class="element file" type="file"> [[ } ]] - <div class="bio"> - <div class="holder"> - <h2>[[- profile.displayName ]]</h2> - [[ if (profile.location) { ]] - <span> - [[- profile.location ]] - </span> - [[ } ]] - [[ if (profile.website && profile.website.length) { ]] - <span> - <a href="[[- profile.website ]]" target="_blank">[[- profile.website ]]</a> - </span> - [[ } ]] - [[ if (profile.twitterName && profile.twitterName.length) { ]] - <span> - <a href="https://twitter.com/[[- profile.twitterName ]]" target="_blank">@[[- profile.twitterName ]]</a> - </span> - [[ } ]] - </div> - </div> - + </span> + </div> + [[ } ]] + <div class="bio"> + <div class="holder"> + <h2>[[- profile.displayName ]]</h2> + [[ if (profile.location) { ]] + <span> + [[- profile.location ]] + </span> + [[ } ]] + [[ if (profile.website && profile.website.length) { ]] + <span> + <a href="[[- profile.website ]]" target="_blank">[[- profile.website ]]</a> + </span> + [[ } ]] + [[ if (profile.twitterName && profile.twitterName.length) { ]] + <span> + <a href="https://twitter.com/[[- profile.twitterName ]]" target="_blank">@[[- profile.twitterName ]]</a> + </span> + [[ } ]] + [[ if (profile.plan_level == 1) { ]] + <span class="plan_level premium">PREMIUM</span> + [[ } else if (profile.plan_level == 2) { ]] + <span class="plan_level pro">PRO</span> + [[ } ]] + </div> + </div> [[ if (projects.length) { ]] + <h1>[[- profile.username ]] has [[- projectCount ]] project[[- projectCount != 1 ? "s" : "" ]]</h1> - [[ include projects/list-projects ]] + [[ } else { ]] - + <h1>Welcome to VVALLS</h1> <div class="projectList about"> <h2> @@ -69,8 +74,11 @@ <h3>This person has no projects.</h3> [[ } ]] </div> + [[ } ]] - </div> + + </div> + [[ include partials/edit-subscription ]] [[ include partials/edit-profile ]] [[ include projects/layouts-modal ]] [[ include projects/edit-project ]] diff --git a/views/staff/_nav.ejs b/views/staff/_nav.ejs index db7bedb..e79ff69 100644 --- a/views/staff/_nav.ejs +++ b/views/staff/_nav.ejs @@ -4,4 +4,5 @@ <a href="/staff/projects">projects</a> <a href="/staff/media">media</a> <a href="/staff/plans">plans</a> + <a href="/staff/subscriptions">subscriptions</a> </nav>
\ No newline at end of file diff --git a/views/staff/subscriptions/index.ejs b/views/staff/subscriptions/index.ejs new file mode 100644 index 0000000..d1c0588 --- /dev/null +++ b/views/staff/subscriptions/index.ejs @@ -0,0 +1,36 @@ +[[ include ../_header ]] + + <h1>Users</h1> + +[[ include ../_nav ]] + + <hr> + +[[ include ../_pagination ]] + +<table id="users"> +[[ subscriptions.forEach(function(subscription){ ]] + <tr> + <td> + <a href="/staff/subscriptions/[[- subscription._id ]]"><div style="background-image:url([[- subscription.user.photo ]])" class="avatar"></div></a> + </td> + <td> + <a href="/staff/subscriptions/[[- subscription._id ]]">[[- subscription.user.username ]]</a> + </td> + <td> + [[- subscription.user.displayName ]] + </td> + <td class="editLinks"> + <a href="/profile/[[- user.username ]]">[view profile]</a> + </td> + <td> + [[- subscription.user.last_seen ]] + </td> + </tr> +[[ }) ]] +</table> + + +[[ include ../_pagination ]] + +[[ include ../_footer ]] diff --git a/views/staff/subscriptions/show.ejs b/views/staff/subscriptions/show.ejs new file mode 100644 index 0000000..e2839a6 --- /dev/null +++ b/views/staff/subscriptions/show.ejs @@ -0,0 +1,12 @@ +[[ include ../_header ]] + <h1>User: [[- subscription.user.username ]]</h1> + +[[ include ../_nav ]] + + <hr> + +<pre> +info to show.. +- link to recurly profile +- link to vvalls profile +- subscription tier + add-ons |
