diff --git a/.meteor/packages b/.meteor/packages index 7a657db..aba2d80 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -6,16 +6,16 @@ meteor-base@1.4.0 # Packages every Meteor app needs to have mobile-experience@1.0.5 # Packages for a great mobile UX -mongo@1.6.0 # The database Meteor supports right now +mongo@1.6.2 # The database Meteor supports right now blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views reactive-var@1.0.11 # Reactive variable for tracker -reactive-dict@1.2.1 # ??? +reactive-dict@1.3.0 # ??? tracker@1.2.0 # Meteor's client-side reactive programming library tomwasd:history-polyfill # Adds IE 8/9 support for HTML5 history. email@1.2.3 # Adds the Meteor/Email package for sending lost password emails -standard-minifier-css@1.5.2 # CSS minifier run for production mode -standard-minifier-js@2.4.0 # JS minifier run for production mode +standard-minifier-css@1.5.3 # CSS minifier run for production mode +standard-minifier-js@2.4.1 # JS minifier run for production mode es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers. poorvavyas:es6-shim ecmascript@0.12.4 # Enable ECMAScript2015+ syntax in app code @@ -60,7 +60,7 @@ juliancwirko:s-alert # Client error/alert handling jcbernack:reactive-aggregate # Allows us to create a new client collection (from the server) with the contents being an aggregate of server data. Note that aggregation can only be done on the server currently as mini-mongo does not support it. ostrio:logger ostrio:loggermongo -dynamic-import@0.5.0 +dynamic-import@0.5.1 markdown@1.0.12 wcrisman:jquery-custom-scrollbar wcrisman:server-side-seo # A custom plugin to take all HTML from the app, and place the templates in a map in a file in the private folder for use at runtime to generate search engine readable html. See code in the /packages folder for this project. diff --git a/.meteor/release b/.meteor/release index 91e05fc..97064e1 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR@1.8.0.2 +METEOR@1.8.1 diff --git a/.meteor/versions b/.meteor/versions index 4f73da0..76c0fb4 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -6,8 +6,8 @@ aldeed:schema-index@3.0.0 aldeed:template-extension@4.1.0 allow-deny@1.1.0 arillo:flow-router-helpers@0.5.2 -autoupdate@1.5.0 -babel-compiler@7.2.4 +autoupdate@1.6.0 +babel-compiler@7.3.4 babel-runtime@1.3.0 base64@1.0.11 binary-heap@1.0.11 @@ -24,7 +24,7 @@ ddp@1.4.0 ddp-client@2.3.3 ddp-common@1.4.0 ddp-rate-limiter@1.0.7 -ddp-server@2.2.0 +ddp-server@2.3.0 deps@1.0.12 diff-sequence@1.1.1 dynamic-import@0.5.1 @@ -35,7 +35,7 @@ ecmascript-runtime-server@0.7.1 ejson@1.1.0 email@1.2.3 es5-shim@4.8.0 -fetch@0.1.0 +fetch@0.1.1 fortawesome:fontawesome@4.7.0 geojson-utils@1.0.10 hot-code-push@1.0.4 @@ -55,7 +55,7 @@ localstorage@1.2.0 logging@1.1.20 manuel:reactivearray@1.0.9 markdown@1.0.12 -meteor@1.9.2 +meteor@1.9.3 meteor-base@1.4.0 meteorhacks:picker@1.0.3 meteorhacks:ssr@2.2.0 @@ -76,24 +76,24 @@ meteortoys:sub@4.0.0 meteortoys:throttle@4.0.0 meteortoys:toggle@4.0.0 meteortoys:toykit@4.0.2 -minifier-css@1.4.1 -minifier-js@2.4.0 +minifier-css@1.4.2 +minifier-js@2.4.1 minimongo@1.4.5 mizzao:bootboxjs@4.4.0 mobile-experience@1.0.5 mobile-status-bar@1.0.14 -modern-browsers@0.1.3 +modern-browsers@0.1.4 modules@0.13.0 modules-runtime@0.10.3 momentjs:moment@2.23.0 -mongo@1.6.0 -mongo-decimal@0.1.0 +mongo@1.6.2 +mongo-decimal@0.1.1 mongo-dev-server@1.1.0 mongo-id@1.0.7 msavin:jetsetter@4.0.0 msavin:mongol@7.0.1 npm-bcrypt@0.9.3 -npm-mongo@3.1.1 +npm-mongo@3.1.2 observe-sequence@1.0.16 ordered-dict@1.1.0 ostrio:cookies@2.3.0 @@ -106,9 +106,9 @@ promise@0.11.2 raix:eventemitter@0.1.3 random@1.1.0 rate-limit@1.0.9 -reactive-dict@1.2.1 +reactive-dict@1.3.0 reactive-var@1.0.11 -reload@1.2.0 +reload@1.3.0 retry@1.1.0 routepolicy@1.1.0 service-configuration@1.0.11 @@ -120,8 +120,8 @@ softwarerero:accounts-t9n@1.3.11 spacebars@1.0.15 spacebars-compiler@1.1.3 srp@1.0.12 -standard-minifier-css@1.5.2 -standard-minifier-js@2.4.0 +standard-minifier-css@1.5.3 +standard-minifier-js@2.4.1 stylus@2.513.14 templating@1.3.2 templating-compiler@1.3.3 @@ -138,6 +138,6 @@ useraccounts:flow-routing@1.14.2 useraccounts:unstyled@1.14.2 wcrisman:jquery-custom-scrollbar@3.0.0 wcrisman:server-side-seo@1.0.0 -webapp@1.7.2 +webapp@1.7.3 webapp-hashing@1.0.9 zimme:active-route@2.3.2 diff --git a/client/main.styl b/client/main.styl index 4aa6b9e..b7a01a2 100644 --- a/client/main.styl +++ b/client/main.styl @@ -175,7 +175,7 @@ h3 z-index: 999 - +@import "../imports/ui/styles/snapTable.import.styl" @import "../imports/ui/styles/effects.import.styl" @import "../imports/ui/styles/buttons.import.styl" @import "../imports/ui/styles/maxHeightLayout.import.styl" @@ -192,6 +192,7 @@ h3 @import "../imports/ui/Home.import.styl" +@import "../imports/ui/Admin/Messages.import.styl" @import "../imports/ui/Admin/UserManagement.import.styl" @import "../imports/ui/Admin/InternshipEditor.import.styl" @import "../imports/ui/Admin/PageEditor.import.styl" diff --git a/imports/api/ContactUsMessages.js b/imports/api/ContactUsMessages.js index c99142f..28ade55 100644 --- a/imports/api/ContactUsMessages.js +++ b/imports/api/ContactUsMessages.js @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Mongo } from 'meteor/mongo'; import { check } from 'meteor/check'; +import { Email } from 'meteor/email'; import SimpleSchema from 'simpl-schema'; let ContactUsMessages = new Mongo.Collection('ContactUsMessages'); @@ -32,7 +33,10 @@ ContactUsMessages.attachSchema(new SimpleSchema({ })); if(Meteor.isServer) Meteor.publish('contactUsMessages', function() { - return ContactUsMessages.find({}); + if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) { + return ContactUsMessages.find({}); + } + else throw new Meteor.Error(403, "Not authorized."); }); if(Meteor.isServer) { @@ -42,10 +46,18 @@ if(Meteor.isServer) { check(email, String); check(message, String); - if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) { - ContactUsMessages.insert({name, email, message, createdAt: new Date()}); + ContactUsMessages.insert({name, email, message, createdAt: new Date()}); + + try { + let settings = Meteor.collections.Settings.findOne(); + + if(settings && settings.forwardEmailsTo && settings.forwardEmailsTo.length > 0) { + Email.send({to: settings.forwardEmailsTo, from: "Do Not Reply ", subject: "Contact Us Message", text: "Email: " + email + "\n\n" + message}); + } + } + catch(err) { + console.log(err); } - else throw new Meteor.Error(403, "Not authorized."); } }); } diff --git a/imports/api/Settings.js b/imports/api/Settings.js new file mode 100644 index 0000000..6d5565f --- /dev/null +++ b/imports/api/Settings.js @@ -0,0 +1,52 @@ +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { check } from 'meteor/check'; +import SimpleSchema from 'simpl-schema'; + +let Settings = new Mongo.Collection('Settings'); +let singletonId; + +Settings.attachSchema(new SimpleSchema({ + forwardEmailsTo: { + type: Array, + label: "Forward Emails To", + optional: true, + defaultValue: [] + }, + 'forwardEmailsTo.$': { + type: String, + label: "Email", + regEx: SimpleSchema.RegEx.Email + } +})); + +if(Meteor.isServer) Meteor.publish('Settings', function() { + if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) { + return Settings.find({}); + } + else throw new Meteor.Error(403, "Not authorized."); +}); + +if(Meteor.isServer) { + let singleton = Settings.findOne({}); + + if(!singleton) { + singletonId = Settings.insert({}); + } + else { + singletonId = singleton._id; + } + + Meteor.methods({ + changeForwardEmailsTo: function(emails) { + if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) { + check(emails, [String]); + + Settings.update({_id: singletonId}, {$set: {forwardEmailsTo: emails}}); + } + else throw new Meteor.Error(403, "Not authorized."); + } + }); +} + +export default Settings; diff --git a/imports/api/index.js b/imports/api/index.js index a7d62e5..1405ea4 100644 --- a/imports/api/index.js +++ b/imports/api/index.js @@ -6,9 +6,10 @@ import ImageUploads from "./Images.js"; import Slideshow from "./Slideshow.js"; import Internship from "./Internship.js"; import ContactUsMessages from "./ContactUsMessages.js"; +import Settings from "./Settings.js"; //Save the collections in the Meteor.collections property for easy access without name conflicts. -Meteor.collections = {Users, UserRoles, Pages, Slideshow, Internship, ContactUsMessages, ImageUploads}; +Meteor.collections = {Users, UserRoles, Pages, Slideshow, Internship, ContactUsMessages, ImageUploads, Settings}; //If this is the server then setup the default admin user if none exist. if(Meteor.isServer) { diff --git a/imports/startup/client/routes.js b/imports/startup/client/routes.js index d45df17..7494091 100644 --- a/imports/startup/client/routes.js +++ b/imports/startup/client/routes.js @@ -88,6 +88,13 @@ pri.route("/Admin/SlideshowPageEditor", { BlazeLayout.render("Admin", {content: "PageEditor"}); } }); +pri.route("/Admin/Messages", { + name: "Messages", + action: function(params, queryParams) { + require("/imports/ui/Admin/Messages.js"); + BlazeLayout.render("Admin", {content: "Messages"}); + } +}); //*** PUBLIC pub.route('/', { diff --git a/imports/ui/Admin/Messages.html b/imports/ui/Admin/Messages.html new file mode 100644 index 0000000..2c52fe5 --- /dev/null +++ b/imports/ui/Admin/Messages.html @@ -0,0 +1,58 @@ + + + \ No newline at end of file diff --git a/imports/ui/Admin/Messages.import.styl b/imports/ui/Admin/Messages.import.styl new file mode 100644 index 0000000..e483e82 --- /dev/null +++ b/imports/ui/Admin/Messages.import.styl @@ -0,0 +1,36 @@ +#messages + .emailContainer + width 100 + position relative + .emailEditor + min-width 200px + width 100% + tr + > .email + width 300px + min-width 100px + > .message + width 50% + min-width 100px + max-width 200px + overflow hidden + text-overflow ellipsis + white-space nowrap + > .date + width 260px + min-width 260px + > .actions + width 80px + min-width 80px + .toggleHandled + color red + .toggleHandled.handled + color green + tr.fullMessage + display none + background-color #b8c1dc + font-weight 800 + div + padding-left 20px + tr.fullMessage.display + display table-row \ No newline at end of file diff --git a/imports/ui/Admin/Messages.js b/imports/ui/Admin/Messages.js new file mode 100644 index 0000000..4af4db9 --- /dev/null +++ b/imports/ui/Admin/Messages.js @@ -0,0 +1,95 @@ +import './Messages.html'; +import swal from "sweetalert2"; + +const PREFIX = "Messages_"; + +Tracker.autorun(function() { + Meteor.subscribe("contactUsMessages"); + Meteor.subscribe("Settings"); +}); + +Template.Messages.onCreated(function() { + Session.set(PREFIX + 'selected', null); +}); +Template.Messages.onRendered(function() { + $(".tableContainer").mCustomScrollbar({ + scrollButtons: {enable:true}, + theme: "light-thick", + scrollbarPosition: "outside", + scrollEasing: "linear" + }); + this.$(".emailEditor").select2({tags: true, tokenSeparators: [';']}); +}); +Template.Messages.helpers({ + messages: function() { + return Meteor.collections.ContactUsMessages.find({}, {sort: {createdAt: 1}}); + }, + selected: function() { + return Session.get(PREFIX + "selected"); + }, + emails: function() { + let emails = []; + let settings = Meteor.collections.Settings.findOne({}); + + //Settings might be undefined if we are still loading. + if(settings) { + emails = settings.forwardEmailsTo; + + //if(emails) { + // emails = emails.split(";"); + //} + } + + return emails; + } +}); +Template.Messages.events({ + 'change .emailEditor': function(e, t) { + let emails = t.$(".emailEditor").select2('data'); + + emails = emails.map((n)=>n.id); + console.log(emails); + Meteor.call("changeForwardEmailsTo", emails, function(err, result) { + if(err) console.log(err); + }); + }, + 'click table.dataTable tr.messageDetails': function(event, template) { + let $tr = template.$(event.currentTarget); + + if(Session.get(PREFIX + "selected") && $tr.data('id') === Session.get(PREFIX + "selected")._id) { + $tr.removeClass('selected'); + $tr.next().removeClass('display'); + Session.set(PREFIX + "selected", undefined); + } + else { + $tr.siblings(".messageDetails").removeClass('selected'); + $tr.siblings(".fullMessage").removeClass('display'); + $tr.addClass('selected'); + $tr.next().addClass('display'); + Session.set(PREFIX + "selected", Meteor.collections.ContactUsMessages.findOne($tr.data('id'))); + } + } +}); + + +Template.Message.onCreated(function() { +}); +Template.Message.helpers({ + date: function() { + return moment(this.createdAt).format("MM/DD/YYYY"); + }, + selected: function() { + return Session.get(PREFIX + "selected"); + }, + handledClass: function() { + return ""; + }, + fullMessage: function() { + return this.message.replace(/[\n\r]/g, "
"); + } +}); +Template.Message.events({ + "click .toggleHandled": function(event, template) { + //TODO: Mark the message as handled. + } +}); \ No newline at end of file diff --git a/imports/ui/layouts/Admin.html b/imports/ui/layouts/Admin.html index d8e2c57..f105617 100644 --- a/imports/ui/layouts/Admin.html +++ b/imports/ui/layouts/Admin.html @@ -45,6 +45,11 @@ News & Notices +
  • + + Messages + +
  • Users diff --git a/imports/ui/styles/snapTable.import.styl b/imports/ui/styles/snapTable.import.styl new file mode 100644 index 0000000..2b1ae52 --- /dev/null +++ b/imports/ui/styles/snapTable.import.styl @@ -0,0 +1,67 @@ + +// A snap table is an abstraction of a full page table that I use repeatedly in projects. All of the table dialogs (editing, new, details, functions, etc) are embedded in the table its self. +// A basic table will have a load more link in the controls, and will load a fixed number of values. A scroll bar attached to the table contents will provide means of accessing all the data. +// The table header will be separated (in a separate HTML table) such that it is always visible. +// Often an action column will be in the mix to provide buttons related to the row, or if in the header will be an action on the table (such as a + button for adding new rows). +.snapTable + display table + content-box border-box + padding 10px 20px + height 100% + width 100% + text-align left + position relative + + .tableControls + display table + width 100% + text-align right + margin-right 20px + a + font-size 12px + font-family "Arial", san-serif + font-weight 800 + color #2d1b8c + text-decoration none + a:hover + text-decoration underline + a.disabled + visibility hidden + .table + table-layout fixed + min-width 100% + tr.selected + background-color #feff00 + .separatedTableHeader + .actions + text-align center + .listContainer + position absolute + top 100px + left 0 + right 0 + bottom 0 + .listRow + display table-row + .listCell + display table-cell + position relative + height 100% + width 100% + .tableContainer + position absolute + top 0 + bottom 0 + left 0 + right 0 + width auto + height auto + border 0 + font-size 12.5px + overflow-y auto + table + table-layout fixed + width 100% + thead + display none + visibility hidden diff --git a/package-lock.json b/package-lock.json index b70a9af..4000a35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "PetitTeton", + "name": "AVEF", "requires": true, "lockfileVersion": 1, "dependencies": { diff --git a/package.json b/package.json index 2b5bbef..7556a8a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "PetitTeton", + "name": "AVEF", "private": true, "scripts": { "start": "meteor run",