Added settings and messages to manage sending web inquiries to one or more email addresses specified via the web management interface.
This commit is contained in:
@@ -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 <no-reply@andersonvalleyeducation.org>", subject: "Contact Us Message", text: "Email: " + email + "\n\n" + message});
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
else throw new Meteor.Error(403, "Not authorized.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
52
imports/api/Settings.js
Normal file
52
imports/api/Settings.js
Normal file
@@ -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;
|
||||
@@ -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) {
|
||||
|
||||
@@ -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('/', {
|
||||
|
||||
58
imports/ui/Admin/Messages.html
Normal file
58
imports/ui/Admin/Messages.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<template name="Messages">
|
||||
<div id="messages" class="snapTable">
|
||||
{{#if Template.subscriptionsReady}}
|
||||
<div class="emailContainer"><label>Forward Messages To:</label>
|
||||
<select class="emailEditor" multiple="multiple">
|
||||
{{#each emails}}
|
||||
<option value="{{this}}" selected>{{this}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="tableControls">
|
||||
<div class="contentControls">
|
||||
<a class="loadMoreLink {{#if disableLoadMore}}disabled{{/if}}" href="javascript:">Load More...</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separatedTableHeader">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="email">Email</th>
|
||||
<th class="message">Message</th>
|
||||
<th class="date">Date</th>
|
||||
<th class="actions">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div class="listContianer">
|
||||
<div class="listRow">
|
||||
<div class="listCell">
|
||||
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
|
||||
<table class="dataTable table table-striped table-hover">
|
||||
<tbody>
|
||||
{{#each messages}}
|
||||
{{> Message}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="Message">
|
||||
<tr class="messageDetails" data-id="{{_id}}">
|
||||
<td class="email tdLarge noselect nonclickable">{{email}}</td>
|
||||
<td class="message tdLarge noselect nonclickable">{{message}}</td>
|
||||
<td class="date tdLarge noselect nonclickable">{{date}}</td>
|
||||
<td class="actions center tdLarge"><i class="toggleHandled {{handledClass}} fa fa-mail fa-lg noselect clickable" aria-hidden="true"></i></td>
|
||||
</tr>
|
||||
<tr class="fullMessage">
|
||||
<td colspan="4"><div>{{{fullMessage}}}</div></td>
|
||||
</tr>
|
||||
</template>
|
||||
36
imports/ui/Admin/Messages.import.styl
vendored
Normal file
36
imports/ui/Admin/Messages.import.styl
vendored
Normal file
@@ -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
|
||||
95
imports/ui/Admin/Messages.js
Normal file
95
imports/ui/Admin/Messages.js
Normal file
@@ -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, "<br/>");
|
||||
}
|
||||
});
|
||||
Template.Message.events({
|
||||
"click .toggleHandled": function(event, template) {
|
||||
//TODO: Mark the message as handled.
|
||||
}
|
||||
});
|
||||
@@ -45,6 +45,11 @@
|
||||
News & Notices
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{isActiveRoute 'Messages'}}">
|
||||
<a href="{{pathFor 'Messages'}}">
|
||||
Messages
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{isActiveRoute 'UserManagement'}}">
|
||||
<a href="{{pathFor 'UserManagement'}}">
|
||||
Users
|
||||
|
||||
67
imports/ui/styles/snapTable.import.styl
vendored
Normal file
67
imports/ui/styles/snapTable.import.styl
vendored
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user