Skip to content

Commit

Permalink
add new Templates collection, order workflow emails
Browse files Browse the repository at this point in the history
Resolves #76
WIP for Issue #423
- updates reaction-collections
- updates reaction-schemas
  • Loading branch information
aaronjudd committed Dec 12, 2015
1 parent 7b33a99 commit aab79c4
Show file tree
Hide file tree
Showing 26 changed files with 4,627 additions and 79 deletions.
4 changes: 2 additions & 2 deletions .meteor/versions
Expand Up @@ -136,12 +136,12 @@ reactioncommerce:launchdock-connect@0.1.2
reactioncommerce:reaction-accounts@1.5.2
reactioncommerce:reaction-analytics@1.2.0
reactioncommerce:reaction-analytics-libs@1.1.0
reactioncommerce:reaction-collections@1.0.1
reactioncommerce:reaction-collections@1.0.2
reactioncommerce:reaction-email-templates@0.1.0
reactioncommerce:reaction-inventory@0.2.0
reactioncommerce:reaction-paypal@1.2.5
reactioncommerce:reaction-sample-data@0.1.0
reactioncommerce:reaction-schemas@1.0.1
reactioncommerce:reaction-schemas@1.0.2
reactioncommerce:reaction-shipping@0.6.1
reactioncommerce:reaction-social@0.4.1
reactioncommerce:reaction-stripe@3.0.2
Expand Down
19 changes: 12 additions & 7 deletions packages/reaction-accounts/server/methods/accounts.js
Expand Up @@ -231,10 +231,15 @@ Meteor.methods({
return doc;
},

/*
/**
* accounts/inviteShopMember
* invite new admin users
* (not consumers) to secure access in the dashboard
* to permissions as specified in packages/roles
* @params {String} shopId - shop to invite user to
* @params {String} email - email of invitee
* @params {String} name - name to address email to
* @returns {Boolean} returns true
*/
"accounts/inviteShopMember": function (shopId, email, name) {
if (!ReactionCore.hasAdminAccess()) {
Expand Down Expand Up @@ -292,13 +297,13 @@ Meteor.methods({
}
}
});
SSR.compileTemplate("shopMemberInvite", ReactionEmailTemplate("templates/accounts/shopMemberInvite.html"));
SSR.compileTemplate("accounts/inviteShopMember", ReactionEmailTemplate("accounts/inviteShopMember"));
try {
Email.send({
to: email,
from: currentUserName + " <" + shop.emails[0].address + ">",
subject: "You have been invited to join " + shop.name,
html: SSR.render("shopMemberInvite", {
html: SSR.render("accounts/inviteShopMember", {
homepage: Meteor.absoluteUrl(),
shop: shop,
currentUserName: currentUserName,
Expand All @@ -310,13 +315,13 @@ Meteor.methods({
throw new Meteor.Error(403, "Unable to send invitation email.");
}
} else {
SSR.compileTemplate("shopMemberInvite", ReactionEmailTemplate("templates/accounts/shopMemberInvite.html"));
SSR.compileTemplate("accounts/inviteShopMember", ReactionEmailTemplate("accounts/inviteShopMember"));
try {
Email.send({
to: email,
from: currentUserName + " <" + shop.emails[0].address + ">",
subject: "You have been invited to join the " + shop.name,
html: SSR.render("shopMemberInvite", {
html: SSR.render("accounts/inviteShopMember", {
homepage: Meteor.absoluteUrl(),
shop: shop,
currentUserName: currentUserName,
Expand All @@ -343,12 +348,12 @@ Meteor.methods({
this.unblock();
email = Meteor.user(userId).emails[0].address;
ReactionCore.configureMailUrl();
SSR.compileTemplate("welcomeNotification", ReactionEmailTemplate("templates/accounts/welcomeNotification.html"));
SSR.compileTemplate("accounts/sendWelcomeEmail", ReactionEmailTemplate("accounts/sendWelcomeEmail"));
Email.send({
to: email,
from: shop.emails[0],
subject: "Welcome to " + shop.name + "!",
html: SSR.render("welcomeNotification", {
html: SSR.render("accounts/sendWelcomeEmail", {
homepage: Meteor.absoluteUrl(),
shop: shop,
user: Meteor.user()
Expand Down
11 changes: 9 additions & 2 deletions packages/reaction-collections/common/collections/collections.js
Expand Up @@ -175,15 +175,22 @@ ReactionCore.Collections.Translations = new Mongo.Collection("Translations");
ReactionCore.Collections.Translations.attachSchema(ReactionCore.Schemas.Translation);

/**
* ReactionCore Collections Accounts
* ReactionCore Collections Layouts
*/
ReactionCore.Collections.Layouts = new Mongo.Collection("Layouts");

ReactionCore.Collections.Layouts.attachSchema(ReactionCore.Schemas.Layouts);

/**
* ReactionCore Collections Templates
*/
ReactionCore.Collections.Templates = new Mongo.Collection("Templates");

ReactionCore.Collections.Templates.attachSchema(ReactionCore.Schemas.Templates);

/**
* ReactionCore Collections CronJobs
*/
ReactionCore.Collections.Jobs = Jobs = new JobCollection('Jobs', {
ReactionCore.Collections.Jobs = Jobs = new JobCollection("Jobs", {
noCollectionSuffix: true
});
4 changes: 2 additions & 2 deletions packages/reaction-collections/package.js
Expand Up @@ -2,7 +2,7 @@ Package.describe({
summary: "Reaction Collections - core collections + hooks, cfs, jobs",
name: "reactioncommerce:reaction-collections",
documentation: "README.md",
version: "1.0.1"
version: "1.0.2"
});

Package.onUse(function (api) {
Expand All @@ -11,7 +11,7 @@ Package.onUse(function (api) {
api.use("underscore");
api.use("ecmascript");

api.use("reactioncommerce:reaction-schemas@1.0.1");
api.use("reactioncommerce:reaction-schemas@1.0.2");
api.use("cfs:standard-packages@0.5.9");
api.use("cfs:storage-adapter@0.2.3");
api.use("cfs:graphicsmagick@0.0.18");
Expand Down
Expand Up @@ -15,7 +15,7 @@

{{#if shipment.shipped}}
<p>Items have been shipped</p>
<button class="btn btn-default" data-event-action="resendShipmentNotification" data-i18n="orderShipping.resendShipmentNotification">Resend Shipment Notification</button>
<button class="btn btn-default" data-event-action="resendNotification" data-i18n="orderShipping.resendNotification">Resend Shipment Notification</button>
{{else}}

<hr>
Expand Down
Expand Up @@ -29,9 +29,9 @@ Template.coreOrderShippingTracking.events({
// Meteor.call("workflow/pushOrderShipmentWorkflow", "coreOrderShipmentWorkflow", "orderShipped", this._id);
},

"click [data-event-action=resendShipmentNotification]": function () {
"click [data-event-action=resendNotification]": function () {
let template = Template.instance();
Meteor.call("orders/sendShipmentNotification", template.order);
Meteor.call("orders/sendNotification", template.order);
},

"click [data-event-action=shipmentPacked]": () => {
Expand Down
3 changes: 1 addition & 2 deletions packages/reaction-core/package.js
Expand Up @@ -48,7 +48,7 @@ Package.onUse(function (api) {
api.use("underscorestring:underscore.string@3.2.2");
api.use("ongoworks:transliteration@0.1.1");
api.use("reactioncommerce:reaction-collections@1.0.1");
api.use("reactioncommerce:reaction-email-templates");
api.use("reactioncommerce:reaction-email-templates@0.1.0");
api.use("aldeed:template-extension@4.0.0", "client");
api.use("aldeed:autoform@5.8.0");
api.use("iron:router@1.0.12");
Expand Down Expand Up @@ -82,7 +82,6 @@ Package.onUse(function (api) {
api.imply("momentjs:moment");
api.imply("utilities:spin", ["client"]);
api.imply("utilities:avatar");
api.imply("meteorhacks:ssr");

// reaction core dependencies
api.addFiles("lib/bower.json", "client");
Expand Down
2 changes: 1 addition & 1 deletion packages/reaction-core/private/data/i18n/en.json
Expand Up @@ -144,7 +144,7 @@
"shipmentPacked": "All Items Packed",
"shipped": "Shipped",
"shippingNotifyCustomer": "Notify the customer that their items will be shipping soon.",
"resendShipmentNotification": "Resend Shipment Notification"
"resendNotification": "Resend Shipment Notification"
},
"cartCompleted": {
"thankYou": "Thank you!",
Expand Down
1 change: 1 addition & 0 deletions packages/reaction-core/server/methods/cart.js
Expand Up @@ -358,6 +358,7 @@ Meteor.methods({
// return
ReactionCore.Log.info("Transitioned cart " + cartId + " to order " +
orderId);
Meteor.call("orders/sendNotification", ReactionCore.Collections.Orders.findOne(orderId));
return orderId;
}
// we should not have made it here, throw error
Expand Down
55 changes: 11 additions & 44 deletions packages/reaction-core/server/methods/orders.js
Expand Up @@ -155,7 +155,7 @@ Meteor.methods({
let shipment = order.shipping[0];

// Attempt to sent email notification
Meteor.call("orders/sendShipmentNotification", order);
Meteor.call("orders/sendNotification", order);

ReactionCore.Collections.Orders.update({
"_id": order._id,
Expand All @@ -177,15 +177,15 @@ Meteor.methods({
* @param {Object} order - order object
* @return {Object} return workflow result
*/
"orders/sendShipmentNotification": function (order) {
"orders/sendNotification": function (order) {
check(order, Object);
this.unblock();

if (order) {
let shop = ReactionCore.Collections.Shops.findOne({});
let shop = ReactionCore.Collections.Shops.findOne(order.shopId);
let shipment = order.shipping[0];

ReactionCore.configureMailUrl();
ReactionCore.Log.info("orders/sendNotification", order.workflow.status);
// handle missing root shop email
if (!shop.emails[0].address) {
shop.emails[0].address = "no-reply@reactioncommerce.com";
Expand All @@ -196,14 +196,16 @@ Meteor.methods({
ReactionCore.Log.warn("No shop email configured. Using anonymous order.");
return true;
}
// TODO: Make this mor easily configurable
SSR.compileTemplate("itemsShipped", ReactionEmailTemplate("templates/orders/itemsShipped.html"));
// email templates can be customized in Templates collection
// loads defaults from reaction-email-templates/templates
let tpl = `orders/${order.workflow.status}`;
SSR.compileTemplate(tpl, ReactionEmailTemplate(tpl));
try {
return Email.send({
to: order.email,
from: `Shipping confirmation <${shop.emails[0].address}>`,
subject: `Your items have shipped from ${shop.name}`,
html: SSR.render("itemsShipped", {
from: `${shop.name} <${shop.emails[0].address}>`,
subject: `Order update from ${shop.name}`,
html: SSR.render(tpl, {
homepage: Meteor.absoluteUrl(),
shop: shop,
// currentUserName: currentUserName,
Expand All @@ -217,41 +219,6 @@ Meteor.methods({
}
}
},

/**
* orders/sendNotification
*
* @summary trigger orderCompleted status and workflow update
* @param {Object} order - order object
* @return {Object} return this.orderCompleted result
*/
"orders/sendNotification": function (order) {
check(order, Object);
this.unblock();

if (order) {
SSR.compileTemplate("itemsShipped", ReactionEmailTemplate("templates/orders/itemsShipped.html"));
let shop = ReactionCore.Collections.Shops.findOne({});
let shipment = orders.shipping[0];

try {
Email.send({
to: email,
from: currentUserName + " <" + shop.emails[0].address + ">",
subject: "Your items have shipped from " + shop.name,
html: SSR.render("itemsShipped", {
homepage: Meteor.absoluteUrl(),
// shop: shop,
// currentUserName: currentUserName,
// invitedUserName: name,
items: shipment.items
})
});
} catch (_error) {
throw new Meteor.Error(403, "Unable to send invitation email.");
}
}
},
/**
* orders/orderCompleted
*
Expand Down
26 changes: 19 additions & 7 deletions packages/reaction-email-templates/README.md
@@ -1,24 +1,36 @@
# Reaction Email Templates
A basic collection of email templates.
# Email Templates
A basic collection of Reaction email templates.

```
meteor add reactioncommerce:reaction-email-templates
```

Example of the implementation of templates from this package:
Provides functionality to load local default email templates, or optionally load from the `Templates` collection.

Typical use of the exported `ReactionEmailTemplate`:

```js
SSR.compileTemplate("itemsShipped", ReactionEmailTemplate("templates/orders/itemsShipped.html"));
SSR.compileTemplate("<template>", ReactionEmailTemplate("<template>"));
try {
return Email.send({
to: email,
from: `email account <${shop.emails[0].address}>`,
subject: `A special message from from ${shop.name}`,
html: SSR.render("itemsShipped", {
from: `${shop.name} <${shop.emails[0].address}>`,
subject: `${shop.name} Update`,
html: SSR.render("<template>", {
homepage: Meteor.absoluteUrl(),
shop: shop,
order: order,
shipment: shipment
})
});
```
## SSR
This package includes `meteorhacks:ssr` which provides Server Side Rendering of templates.
`meteorhacks:ssr` is implied and exports `SSR`.
## Templates
Templates are located in `reaction-email-templates/templates` and are named either after a workflow status, or a method that triggers their load.
Templates can be defined in the `Shops.layout` and `Templates` collections, this collection can be used for customization of the default template collections, or to provide additional templates.
24 changes: 20 additions & 4 deletions packages/reaction-email-templates/package.js
Expand Up @@ -8,11 +8,27 @@ Package.describe({

Package.onUse(function (api) {
api.versionsFrom("METEOR@1.2");
// Email Templates
api.use("ecmascript");
api.use("reactioncommerce:reaction-collections@1.0.2");
api.use("meteorhacks:ssr@2.2.0");
api.imply("meteorhacks:ssr");

// define ReactionEmailTemplate
api.addFiles("templates.js", "server");
api.addAssets("templates/accounts/welcomeNotification.html", "server");
api.addAssets("templates/accounts/shopMemberInvite.html", "server");
api.addAssets("templates/orders/itemsShipped.html", "server");

// Email Templates
api.addAssets("templates/checkout/checkoutLogin.html", "server");
api.addAssets("templates/coreDefault.html", "server");

api.addAssets("templates/orders/new.html", "server");
api.addAssets("templates/orders/coreOrderCompleted.html", "server");
api.addAssets("templates/orders/coreOrderShippingInvoice.html", "server");
api.addAssets("templates/orders/coreOrderShippingSummary.html", "server");
api.addAssets("templates/orders/coreOrderShippingTracking.html", "server");

// Accounts Email Templates
api.addAssets("templates/accounts/sendWelcomeEmail.html", "server");
api.addAssets("templates/accounts/inviteShopMember.html", "server");

api.export("ReactionEmailTemplate");
});
35 changes: 33 additions & 2 deletions packages/reaction-email-templates/templates.js
@@ -1,3 +1,34 @@
ReactionEmailTemplate = function (file) {
return Assets.getText(file);
/**
* ReactionEmailTemplate - Returns a template source for SSR consumption
* layout must be defined + template
* @param {String} template name of the template in either Layouts or fs
* @returns {Object} returns source
*/
ReactionEmailTemplate = function (template) {
check(template, String);
let source;

const lang = Meteor.call("shop/getLocale").locale.languages;
// using layout where in the future a more comprehensive rule based
// filter of the email templates can be implemented.
const tpl = ReactionCore.Collections.Layouts.findOne({
template: template
});

if (tpl) {
const tplSource = ReactionCore.Collections.Templates.findOne({
template: template,
language: lang
});
if (tplSource.source) {
return tplSource.source;
}
}
let file = `templates/${template}.html`;
try {
source = Assets.getText(file);
} catch (e) { // default blank template
source = Assets.getText("templates/coreDefault.html");
}
return source;
};
Empty file.
Empty file.

0 comments on commit aab79c4

Please sign in to comment.