nitro-web 0.0.33 → 0.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/auth/auth.api.js +16 -12
- package/components/partials/element/button.tsx +4 -4
- package/package.json +1 -1
- package/server/email/index.js +24 -18
- package/server/email/partials/layout1.swig +1 -1
- package/server/email/partials/vert-25.swig +1 -1
- package/server/email/reset-password.html +10 -2
- package/server/email/welcome.html +12 -3
|
@@ -6,7 +6,7 @@ import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt'
|
|
|
6
6
|
import db from 'monastery'
|
|
7
7
|
import jsonwebtoken from 'jsonwebtoken'
|
|
8
8
|
import { sendEmail } from 'nitro-web/server'
|
|
9
|
-
import
|
|
9
|
+
import { isArray, pick, isString, ucFirst, fullNameSplit } from 'nitro-web/util'
|
|
10
10
|
|
|
11
11
|
let config = {}
|
|
12
12
|
const JWT_SECRET = process.env.JWT_SECRET || 'replace_this_with_secure_env_secret'
|
|
@@ -32,9 +32,13 @@ export default {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
function setup(middleware, _config) {
|
|
35
|
+
// Setup is called automatically when the server starts
|
|
35
36
|
// Set config values
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
const configKeys = ['clientUrl', 'emailFrom', 'env', 'mailgunDomain', 'mailgunKey', 'masterPassword', 'name']
|
|
38
|
+
config = pick(_config, configKeys)
|
|
39
|
+
for (const key of configKeys) {
|
|
40
|
+
if (!config[key]) throw new Error(`Missing config value for: config.${key}`)
|
|
41
|
+
}
|
|
38
42
|
|
|
39
43
|
passport.use(
|
|
40
44
|
new passportLocal.Strategy(
|
|
@@ -102,7 +106,7 @@ async function signup(req, res) {
|
|
|
102
106
|
sendEmail({
|
|
103
107
|
config: config,
|
|
104
108
|
template: 'welcome',
|
|
105
|
-
to: `${
|
|
109
|
+
to: `${ucFirst(user.firstName)}<${user.email}>`,
|
|
106
110
|
}).catch(console.error)
|
|
107
111
|
res.send(await signinAndGetState(user, req.query.desktop))
|
|
108
112
|
} catch (err) {
|
|
@@ -133,7 +137,7 @@ function signout(req, res) {
|
|
|
133
137
|
async function resetInstructions(req, res) {
|
|
134
138
|
try {
|
|
135
139
|
let email = (req.body.email || '').trim().toLowerCase()
|
|
136
|
-
if (!email || !
|
|
140
|
+
if (!email || !isString(email)) throw { title: 'email', detail: 'The email you entered is incorrect.' }
|
|
137
141
|
|
|
138
142
|
let user = await db.user.findOne({ query: { email }, _privateData: true })
|
|
139
143
|
if (!user) throw { title: 'email', detail: 'The email you entered is incorrect.' }
|
|
@@ -145,7 +149,7 @@ async function resetInstructions(req, res) {
|
|
|
145
149
|
sendEmail({
|
|
146
150
|
config: config,
|
|
147
151
|
template: 'reset-password',
|
|
148
|
-
to: `${
|
|
152
|
+
to: `${ucFirst(user.firstName)}<${email}>`,
|
|
149
153
|
data: {
|
|
150
154
|
token: resetToken + (req.query.hasOwnProperty('desktop') ? '?desktop' : ''),
|
|
151
155
|
},
|
|
@@ -271,21 +275,21 @@ async function validatePassword(password='', password2) {
|
|
|
271
275
|
}
|
|
272
276
|
}
|
|
273
277
|
|
|
274
|
-
export async function userCreate({ name,
|
|
278
|
+
export async function userCreate({ name, business, email, password }) {
|
|
275
279
|
try {
|
|
276
280
|
const options = { blacklist: ['-_id'] }
|
|
277
281
|
const userId = db.id()
|
|
278
282
|
const companyData = {
|
|
279
283
|
_id: db.id(),
|
|
280
|
-
business
|
|
284
|
+
...(business ? { business } : {}),
|
|
281
285
|
users: [{ _id: userId, role: 'owner', status: 'active' }],
|
|
282
286
|
}
|
|
283
287
|
const userData = {
|
|
284
288
|
_id: userId,
|
|
285
289
|
company: companyData._id,
|
|
286
290
|
email: email,
|
|
287
|
-
firstName:
|
|
288
|
-
lastName:
|
|
291
|
+
firstName: fullNameSplit(name)[0],
|
|
292
|
+
lastName: fullNameSplit(name)[1],
|
|
289
293
|
password: password ? await (await import('bcrypt')).hash(password, 10) : undefined,
|
|
290
294
|
}
|
|
291
295
|
|
|
@@ -298,7 +302,7 @@ export async function userCreate({ name, businessName, email, password }) {
|
|
|
298
302
|
|
|
299
303
|
// Throw all the errors from at once
|
|
300
304
|
const errors = results.filter(o => o.status == 'rejected').reduce((acc, o) => {
|
|
301
|
-
if (
|
|
305
|
+
if (isArray(o.reason)) acc.push(...o.reason)
|
|
302
306
|
else throw o.reason
|
|
303
307
|
return acc
|
|
304
308
|
}, [])
|
|
@@ -312,7 +316,7 @@ export async function userCreate({ name, businessName, email, password }) {
|
|
|
312
316
|
return await findUserFromProvider({ _id: userId })
|
|
313
317
|
|
|
314
318
|
} catch (err) {
|
|
315
|
-
if (!
|
|
319
|
+
if (!isArray(err)) throw err
|
|
316
320
|
throw err.map((o) => {
|
|
317
321
|
if (o.title == 'firstName') o.title = 'name'
|
|
318
322
|
return o
|
|
@@ -29,16 +29,16 @@ export function Button({
|
|
|
29
29
|
const iconPosition = IconLeft ? 'left' : IconLeftEnd ? 'leftEnd' : IconRight ? 'right' : IconRightEnd ? 'rightEnd' : 'none'
|
|
30
30
|
const base =
|
|
31
31
|
'relative inline-block text-center font-medium shadow-sm focus-visible:outline focus-visible:outline-2 ' +
|
|
32
|
-
'focus-visible:outline-offset-2 text-white'
|
|
32
|
+
'focus-visible:outline-offset-2 text-white [&>.loader]:border-white'
|
|
33
33
|
|
|
34
34
|
// Button colors, you can use custom colors by using className instead
|
|
35
35
|
const colors = {
|
|
36
36
|
primary: 'bg-primary hover:bg-primary-hover',
|
|
37
37
|
secondary: 'bg-secondary hover:bg-secondary-hover',
|
|
38
38
|
black: 'bg-black hover:bg-gray-700',
|
|
39
|
-
white: 'bg-white
|
|
39
|
+
white: 'bg-white ring-1 ring-inset ring-gray-300 hover:bg-gray-50 text-gray-900 [&>.loader]:border-black',
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
// Button sizes
|
|
43
43
|
const sizes = {
|
|
44
44
|
xs: 'px-2 py-1 px-button-x-xs py-button-y-xs text-xs rounded',
|
|
@@ -67,7 +67,7 @@ export function Button({
|
|
|
67
67
|
isLoading &&
|
|
68
68
|
<span className={
|
|
69
69
|
'loader !opacity-100 absolute top-[50%] left-[50%] w-[1rem] h-[1rem] ml-[-0.5rem] mt-[-0.5rem] ' +
|
|
70
|
-
'rounded-full animate-spin border-2 !border-t-transparent
|
|
70
|
+
'rounded-full animate-spin border-2 !border-t-transparent'
|
|
71
71
|
} />
|
|
72
72
|
}
|
|
73
73
|
</button>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitro-web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.34",
|
|
4
4
|
"repository": "github:boycce/nitro-web",
|
|
5
5
|
"homepage": "https://boycce.github.io/nitro-web/",
|
|
6
6
|
"description": "Nitro is a battle-tested, modular base project to turbocharge your projects, styled using Tailwind 🚀",
|
package/server/email/index.js
CHANGED
|
@@ -7,21 +7,21 @@ import inlineCss from 'inline-css'
|
|
|
7
7
|
import { dirname, join } from 'path'
|
|
8
8
|
import { fileURLToPath } from 'url'
|
|
9
9
|
import path from 'path'
|
|
10
|
-
import { getDirectories } from 'nitro-web/util'
|
|
10
|
+
import { getDirectories, ucFirst } from 'nitro-web/util'
|
|
11
11
|
|
|
12
12
|
let templates = {}
|
|
13
13
|
let nodemailerMailgun = undefined
|
|
14
14
|
const _dirname = dirname(fileURLToPath(import.meta.url)) + '/'
|
|
15
15
|
|
|
16
|
-
export async function sendEmail({ template, to, bcc, data
|
|
16
|
+
export async function sendEmail({ template, to, bcc, data, from, replyTo, recipientVariables, subject, test, skipCssInline, config }) {
|
|
17
17
|
/**
|
|
18
18
|
* Email recipient a predefined template with data and/or recipientVariables
|
|
19
19
|
*
|
|
20
20
|
* @param {string} template = e.g. 'reset-password' or html
|
|
21
|
-
* @param {string} to - e.g. "Bruce
|
|
22
|
-
* @param {object} config - e.g. { mailgunKey, mailgunDomain, emailFrom, clientUrl }
|
|
21
|
+
* @param {string} to - e.g. "Bruce Wayne<bruce@wayneenterprises.com>, ..."
|
|
22
|
+
* @param {object} config - e.g. { mailgunKey, mailgunDomain, emailFrom, clientUrl, name }
|
|
23
23
|
* @param {string} <bcc> - e.g. "Chuck Norris<chuck@gmail.com>" (not sent in development)
|
|
24
|
-
* @param {object} <data> - recipientVariables[to] shorthand
|
|
24
|
+
* @param {object} <data> - recipientVariables[to] shorthand, data is copied to all recipients
|
|
25
25
|
* @param {string} <from> - e.g. "Chuck Norris<chuck@gmail.com>"
|
|
26
26
|
* @param {string} <replyTo> - e.g. "Chuck Norris<chuck@gmail.com>"
|
|
27
27
|
* @param {object} <recipientVariables> - mailgun recipient-variables for batch sending
|
|
@@ -32,8 +32,12 @@ export async function sendEmail({ template, to, bcc, data={}, from, replyTo, rec
|
|
|
32
32
|
*/
|
|
33
33
|
if (!config) {
|
|
34
34
|
throw new Error('sendEmail: `config` missing')
|
|
35
|
-
} else if (!config.emailFrom
|
|
36
|
-
throw new Error('sendEmail: `config.emailFrom`
|
|
35
|
+
} else if (!config.emailFrom) {
|
|
36
|
+
throw new Error('sendEmail: `config.emailFrom` is missing')
|
|
37
|
+
} else if (!config.clientUrl) {
|
|
38
|
+
throw new Error('sendEmail: `config.clientUrl` is missing')
|
|
39
|
+
} else if (!config.name) {
|
|
40
|
+
throw new Error('sendEmail: `config.name` is missing')
|
|
37
41
|
} else if (!test && (!config.mailgunKey || !config.mailgunDomain)) {
|
|
38
42
|
throw new Error('sendEmail: `config.mailgunKey` or `config.mailgunDomain` is missing')
|
|
39
43
|
} else if (!template) {
|
|
@@ -52,27 +56,30 @@ export async function sendEmail({ template, to, bcc, data={}, from, replyTo, rec
|
|
|
52
56
|
// From, replayTo
|
|
53
57
|
from = from || config.emailFrom
|
|
54
58
|
replyTo = replyTo || config.emailReplyTo || from
|
|
55
|
-
|
|
56
|
-
// Data is recipientVariables[to] shorthand
|
|
57
|
-
if (data) {
|
|
58
|
-
recipientVariables = { [getNameEmail(to)[1]]: data }
|
|
59
|
-
}
|
|
59
|
+
const toSplit = to.split(',')
|
|
60
60
|
|
|
61
61
|
// Add default recipientVariables
|
|
62
|
-
|
|
62
|
+
if (!recipientVariables) recipientVariables = {}
|
|
63
|
+
|
|
64
|
+
// Add default values to recipientVariables
|
|
65
|
+
for (let toNameEmail of toSplit) {
|
|
66
|
+
const [toName, toEmail] = getNameEmail(toNameEmail)
|
|
63
67
|
recipientVariables[toEmail] = {
|
|
68
|
+
...(data || {}),
|
|
69
|
+
configName: ucFirst(config.name),
|
|
64
70
|
domain: config.clientUrl,
|
|
65
|
-
email: toEmail,
|
|
66
|
-
greet: data.name || getNameEmail(to)[0]? 'Hi ' + (data.name || getNameEmail(to)[0]) : 'Hello',
|
|
67
|
-
name: getNameEmail(to)[0],
|
|
68
71
|
replyToEmail: getNameEmail(replyTo)[1],
|
|
69
72
|
replyToName: getNameEmail(replyTo)[0],
|
|
70
|
-
|
|
73
|
+
email: toEmail,
|
|
74
|
+
greet: toName ? 'Hi ' + toName : 'Hello',
|
|
75
|
+
name: toName,
|
|
76
|
+
...(recipientVariables[toEmail] || {}),
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
let settings = {
|
|
75
81
|
bcc: bcc,
|
|
82
|
+
emailTemplateDir: getDirectories(path, config.pwd).emailTemplateDir,
|
|
76
83
|
from: from,
|
|
77
84
|
isDev: config.clientUrl.match(/:/), // possibly use config.env here
|
|
78
85
|
recipientVariables: recipientVariables,
|
|
@@ -83,7 +90,6 @@ export async function sendEmail({ template, to, bcc, data={}, from, replyTo, rec
|
|
|
83
90
|
test: config.emailTestMode || test,
|
|
84
91
|
to: to,
|
|
85
92
|
url: config.clientUrl,
|
|
86
|
-
emailTemplateDir: getDirectories(path, config.pwd).emailTemplateDir,
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
// Grab html and send
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
<tr>
|
|
32
32
|
<td width="180px" align="left">
|
|
33
33
|
<a href="%recipient.domain%" target="_blank">
|
|
34
|
-
<img src="%recipient.domain%/assets/imgs/logo/logo
|
|
34
|
+
<img src="%recipient.domain%/assets/imgs/logo/logo.svg" width="auto" height="22" border="0">
|
|
35
35
|
</a>
|
|
36
36
|
</td>
|
|
37
37
|
<td align="right">
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- You can view templates at http://localhost:3001/email/reset-password
|
|
3
|
+
- To modify the css, add your own /server/email/partials/email.css file
|
|
4
|
+
- You can copy any nitro email .swig/html template to your project, and modify it
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
<!-- extends references the default nitro layout file -->
|
|
1
8
|
{% extends "partials/layout1.swig" %}
|
|
9
|
+
|
|
10
|
+
<!-- block content is the content of the email -->
|
|
2
11
|
{% block content %}
|
|
3
12
|
|
|
4
13
|
[[ subject = Reset your Password ]]
|
|
@@ -15,7 +24,6 @@
|
|
|
15
24
|
</span><br/>
|
|
16
25
|
<br/>
|
|
17
26
|
Thanks,<br/>
|
|
18
|
-
<b>The
|
|
19
|
-
|
|
27
|
+
<b>The %recipient.configName% Team</b>
|
|
20
28
|
|
|
21
29
|
{% endblock %}
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- You can view templates at http://localhost:3001/email/welcome
|
|
3
|
+
- To modify the css, add your own /server/email/partials/email.css file
|
|
4
|
+
- You can copy any nitro email .swig/html template to your project, and modify it
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
<!-- extends references the default nitro layout file -->
|
|
1
8
|
{% extends "partials/layout1.swig" %}
|
|
9
|
+
|
|
10
|
+
<!-- block content is the content of the email -->
|
|
2
11
|
{% block content %}
|
|
3
12
|
|
|
4
|
-
[[ subject = Welcome to
|
|
13
|
+
[[ subject = Welcome to %recipient.configName% ]]
|
|
5
14
|
<b>%recipient.greet%</b>,<br/>
|
|
6
15
|
<br/>
|
|
7
|
-
Thanks for trying out
|
|
16
|
+
Thanks for trying out %recipient.configName%! <br/>
|
|
8
17
|
<br/>
|
|
9
18
|
If you have any feature requests, feedback or questions, please don't hesitate to reach out to us at <a href="mailto:%recipient.replyToEmail%">%recipient.replyToEmail%</a>.<br/>
|
|
10
19
|
<br/>
|
|
@@ -16,6 +25,6 @@
|
|
|
16
25
|
</span><br/>
|
|
17
26
|
<br/>
|
|
18
27
|
Thanks,<br/>
|
|
19
|
-
<b>The
|
|
28
|
+
<b>The %recipient.configName% Team</b>
|
|
20
29
|
|
|
21
30
|
{% endblock %}
|