nitro-web 0.0.62 → 0.0.64

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.
@@ -11,28 +11,31 @@ import { isArray, pick, isString, ucFirst, fullNameSplit } from 'nitro-web/util'
11
11
  let authConfig = null
12
12
  const JWT_SECRET = process.env.JWT_SECRET || 'replace_this_with_secure_env_secret'
13
13
 
14
- export default {
15
- routes: {
16
- 'get /api/store': ['store'],
17
- 'get /api/signout': ['signout'],
18
- 'post /api/signin': ['signin'],
19
- 'post /api/signup': ['signup'],
20
- 'post /api/reset-instructions': ['resetInstructions'],
21
- 'post /api/reset-password': ['resetPassword'],
22
- 'delete /api/account/:uid': ['isUser', 'remove'],
23
- },
14
+ export const routes = {
15
+ // Routes
16
+ 'get /api/store': [store],
17
+ 'get /api/signout': [signout],
18
+ 'post /api/signin': [signin],
19
+ 'post /api/signup': [signup],
20
+ 'post /api/reset-instructions': [resetInstructions],
21
+ 'post /api/reset-password': [resetPassword],
22
+ 'post /api/invite-instructions': [inviteInstructions],
23
+ 'post /api/invite-accept': [resetPassword],
24
+ 'delete /api/account/:uid': [remove],
25
+
26
+ // Overridable helpers
24
27
  setup: setup,
25
- store: store,
26
- signup: signup,
27
- signin: signin,
28
- signout: signout,
29
- resetInstructions: resetInstructions,
30
- resetPassword: resetPassword,
31
- remove: remove,
28
+ findUserFromProvider: findUserFromProvider,
29
+ getStore: getStore,
30
+ signinAndGetStore: signinAndGetStore,
31
+ tokenCreate: tokenCreate,
32
+ tokenParse: tokenParse,
33
+ userCreate: userCreate,
34
+ validatePassword: validatePassword,
32
35
  }
33
36
 
34
37
  function setup(middleware, _config) {
35
- // Setup is called automatically when the server starts
38
+ // routes.setup is called automatically when express starts
36
39
  // Set config values
37
40
  const configKeys = ['clientUrl', 'emailFrom', 'env', 'name', 'mailgunDomain', 'mailgunKey', 'masterPassword', 'isNotMultiTenant']
38
41
  authConfig = pick(_config, configKeys)
@@ -45,7 +48,7 @@ function setup(middleware, _config) {
45
48
  { usernameField: 'email' },
46
49
  async (email, password, next) => {
47
50
  try {
48
- const user = await findUserFromProvider({ email }, password)
51
+ const user = await this.findUserFromProvider({ email }, password)
49
52
  next(null, user)
50
53
  } catch (err) {
51
54
  next(err.message)
@@ -62,7 +65,7 @@ function setup(middleware, _config) {
62
65
  },
63
66
  async (payload, done) => {
64
67
  try {
65
- const user = await findUserFromProvider({ _id: payload._id })
68
+ const user = await this.findUserFromProvider({ _id: payload._id })
66
69
  if (!user) return done(null, false)
67
70
  return done(null, user)
68
71
  } catch (err) {
@@ -97,24 +100,26 @@ function setup(middleware, _config) {
97
100
  }
98
101
 
99
102
  async function store(req, res) {
100
- res.json(await getStore(req.user))
103
+ res.json(await this.getStore(req.user))
101
104
  }
102
105
 
103
106
  async function signup(req, res) {
104
107
  try {
105
- let user = await userCreate(req.body)
108
+ const desktop = req.query.desktop
109
+ let user = await this.userCreate(req.body, this.findUserFromProvider)
106
110
  sendEmail({
107
111
  config: authConfig,
108
112
  template: 'welcome',
109
113
  to: `${ucFirst(user.firstName)}<${user.email}>`,
110
114
  }).catch(console.error)
111
- res.send(await signinAndGetState(user, req.query.desktop))
115
+ res.send(await this.signinAndGetStore(user, desktop, this.getStore))
112
116
  } catch (err) {
113
117
  res.error(err)
114
118
  }
115
119
  }
116
120
 
117
121
  function signin(req, res) {
122
+ const desktop = req.query.desktop
118
123
  if (!req.body.email) return res.error('email', 'The email you entered is incorrect.')
119
124
  if (!req.body.password) return res.error('password', 'The password you entered is incorrect.')
120
125
 
@@ -122,7 +127,7 @@ function signin(req, res) {
122
127
  if (err) return res.error(err)
123
128
  if (!user && info) return res.error('email', info.message)
124
129
  try {
125
- const response = await signinAndGetState(user, req.query.desktop)
130
+ const response = await this.signinAndGetStore(user, desktop, this.getStore)
126
131
  res.send(response)
127
132
  } catch (err) {
128
133
  res.error(err)
@@ -137,7 +142,7 @@ function signout(req, res) {
137
142
  async function resetInstructions(req, res) {
138
143
  try {
139
144
  let email = (req.body.email || '').trim().toLowerCase()
140
- if (!email || !isString(email)) throw { title: 'email', detail: 'The email you entered is incorrect.' }
145
+ if (!email) throw { title: 'email', detail: 'The email you entered is incorrect.' }
141
146
 
142
147
  let user = await db.user.findOne({ query: { email }, _privateData: true })
143
148
  if (!user) throw { title: 'email', detail: 'The email you entered is incorrect.' }
@@ -162,11 +167,13 @@ async function resetInstructions(req, res) {
162
167
  async function resetPassword(req, res) {
163
168
  try {
164
169
  const { token, password, password2 } = req.body
170
+ const name = req.path.includes('invite') ? 'inviteToken' : 'resetToken'
171
+ const desktop = req.query.desktop
165
172
  const id = tokenParse(token)
166
173
  await validatePassword(password, password2)
167
174
 
168
- let user = await db.user.findOne({ query: id, blacklist: ['-resetToken'], _privateData: true })
169
- if (!user || user.resetToken !== token) throw new Error('Sorry your email token is invalid or has already been used.')
175
+ let user = await db.user.findOne({ query: id, blacklist: ['-' + name], _privateData: true })
176
+ if (!user || user[name] !== token) throw new Error('Sorry your token is invalid or has already been used.')
170
177
 
171
178
  await db.user.update({
172
179
  query: user._id,
@@ -174,14 +181,53 @@ async function resetPassword(req, res) {
174
181
  password: await bcrypt.hash(password, 10),
175
182
  resetToken: '',
176
183
  },
177
- blacklist: ['-resetToken', '-password'],
184
+ blacklist: ['-' + name, '-password'],
178
185
  })
179
- res.send(await signinAndGetState({ ...user, resetToken: undefined }, req.query.desktop))
186
+ res.send(await this.signinAndGetStore({ ...user, [name]: undefined }, desktop, this.getStore))
180
187
  } catch (err) {
181
188
  res.error(err)
182
189
  }
183
190
  }
184
191
 
192
+ async function inviteInstructions(req, res) {
193
+ try {
194
+ // Check if user is admin here rather than in middleware (which may not exist yet)
195
+ if (req.user.type != 'admin') {
196
+ throw new Error('You are not authorized to invite users.')
197
+ }
198
+ const inviteToken = await tokenCreate()
199
+ const userData = await db.user.validate({
200
+ ...pick(req.body, ['email', 'firstName', 'lastName']),
201
+ status: 'invited',
202
+ inviteToken: inviteToken,
203
+ })
204
+
205
+ // Check if user already exists
206
+ if (await db.user.findOne({ query: { email: userData.email } })) {
207
+ throw { title: 'email', detail: 'User already exists.' }
208
+ }
209
+
210
+ // Create user
211
+ const user = await db.user.insert({
212
+ data: userData,
213
+ })
214
+
215
+ // Send email
216
+ res.send(user)
217
+ sendEmail({
218
+ config: authConfig,
219
+ template: 'invite-user',
220
+ to: `${ucFirst(userData.firstName)}<${userData.email}>`,
221
+ data: {
222
+ token: inviteToken + (req.query.hasOwnProperty('desktop') ? '?desktop' : ''),
223
+ },
224
+ }).catch(err => console.error('sendEmail(..) mailgun error', err))
225
+
226
+ } catch (err) {
227
+ return res.error(err)
228
+ }
229
+ }
230
+
185
231
  async function remove(req, res) {
186
232
  try {
187
233
  const uid = db.id(req.params.uid || 'badid')
@@ -208,7 +254,7 @@ async function remove(req, res) {
208
254
  }
209
255
  }
210
256
 
211
- /* ---- Auth helpers ------------------------- */
257
+ /* ---- Overridable helpers ------------------ */
212
258
 
213
259
  export async function findUserFromProvider(query, passwordToCheck) {
214
260
  /**
@@ -260,7 +306,7 @@ export async function getStore(user) {
260
306
  }
261
307
  }
262
308
 
263
- export async function signinAndGetState(user, isDesktop) {
309
+ export async function signinAndGetStore(user, isDesktop, getStore) {
264
310
  if (user.loginActive === false) throw 'This user is not available.'
265
311
  user.desktop = isDesktop
266
312
 
@@ -269,30 +315,7 @@ export async function signinAndGetState(user, isDesktop) {
269
315
  return { ...store, jwt }
270
316
  }
271
317
 
272
- export function tokenCreate(id) {
273
- return new Promise((resolve) => {
274
- crypto.randomBytes(16, (err, buff) => {
275
- let hash = buff.toString('hex') // 32 chars
276
- resolve(`${hash}${id || ''}:${Date.now()}`)
277
- })
278
- })
279
- }
280
-
281
- export function tokenParse(token) {
282
- let split = (token || '').split(':')
283
- let hash = split[0].slice(0, 32)
284
- let userId = split[0].slice(32)
285
- let time = split[1]
286
- if (!hash || !userId || !time) {
287
- throw { title: 'error', detail: 'Sorry your code is invalid.' }
288
- } else if (parseFloat(time) + 1000 * 60 * 60 * 24 < Date.now()) {
289
- throw { title: 'error', detail: 'Sorry your code has timed out.' }
290
- } else {
291
- return userId
292
- }
293
- }
294
-
295
- export async function userCreate({ name, business, email, password }) {
318
+ export async function userCreate({ name, business, email, password, findUserFromProvider }) {
296
319
  try {
297
320
  const options = { blacklist: ['-_id'] }
298
321
  const isMultiTenant = !authConfig.isNotMultiTenant
@@ -342,6 +365,29 @@ export async function userCreate({ name, business, email, password }) {
342
365
  }
343
366
  }
344
367
 
368
+ export function tokenCreate(id) {
369
+ return new Promise((resolve) => {
370
+ crypto.randomBytes(16, (err, buff) => {
371
+ let hash = buff.toString('hex') // 32 chars
372
+ resolve(`${hash}${id || ''}:${Date.now()}`)
373
+ })
374
+ })
375
+ }
376
+
377
+ export function tokenParse(token) {
378
+ let split = (token || '').split(':')
379
+ let hash = split[0].slice(0, 32)
380
+ let userId = split[0].slice(32)
381
+ let time = split[1]
382
+ if (!hash || !userId || !time) {
383
+ throw { title: 'error', detail: 'Sorry your code is invalid.' }
384
+ } else if (parseFloat(time) + 1000 * 60 * 60 * 24 < Date.now()) {
385
+ throw { title: 'error', detail: 'Sorry your code has timed out.' }
386
+ } else {
387
+ return userId
388
+ }
389
+ }
390
+
345
391
  export async function validatePassword(password='', password2) {
346
392
  // let hasLowerChar = password.match(/[a-z]/)
347
393
  // let hasUpperChar = password.match(/[A-Z]/)
@@ -359,4 +405,4 @@ export async function validatePassword(password='', password2) {
359
405
  } else if (typeof password2 != 'undefined' && password !== password2) {
360
406
  throw [{ title: 'password2', detail: 'Your passwords need to match.' }]
361
407
  }
362
- }
408
+ }
@@ -7,16 +7,14 @@ let stripe = undefined
7
7
  let stripeProducts = []
8
8
  let config = {}
9
9
 
10
- export default {
11
- routes: {
12
- 'post /api/stripe/webhook': ['stripeWebhook'],
13
- 'post /api/stripe/create-billing-portal-session': ['isUser', 'billingPortalSessionCreate'],
14
- 'get /api/stripe/upcoming-invoices': ['isUser', 'upcomingInvoicesFind'],
15
- },
10
+ export const routes = {
11
+ // Routes
12
+ 'post /api/stripe/webhook': [stripeWebhook],
13
+ 'post /api/stripe/create-billing-portal-session': [billingPortalSessionCreate],
14
+ 'get /api/stripe/upcoming-invoices': [upcomingInvoicesFind],
15
+
16
+ // Overridable helpers
16
17
  setup: setup,
17
- stripeWebhook: stripeWebhook,
18
- billingPortalSessionCreate: billingPortalSessionCreate,
19
- upcomingInvoicesFind: upcomingInvoicesFind,
20
18
  }
21
19
 
22
20
  function setup(middleware, _config) {
@@ -104,7 +102,80 @@ async function upcomingInvoicesFind(req, res) {
104
102
  }
105
103
  }
106
104
 
107
- /* Private webhook actions */
105
+ /* ---- Overridable helpers ------------------ */
106
+
107
+ async function error(req, res, err) {
108
+ if (err && err.response && err.response.body) console.log(err.response.body)
109
+ if (util.isString(err) && err.match(/Cannot find company with id/)) {
110
+ res.json({ user: 'no company found' })
111
+ } else res.error(err)
112
+ }
113
+
114
+ export async function getProducts() {
115
+ /**
116
+ * Returns all products and caches it on the app
117
+ * @returns {Array} products
118
+ */
119
+ try {
120
+ if (stripeProducts) return stripeProducts
121
+ if (!config.stripeSecretKey) {
122
+ stripeProducts = []
123
+ throw new Error('Missing process.env.stripeSecretKey for retrieving products')
124
+ }
125
+
126
+ let products = (await stripe.products.list({ limit: 100, active: true })).data
127
+ let prices = (await stripe.prices.list({ limit: 100, active: true, expand: ['data.tiers'] })).data
128
+
129
+ return (stripeProducts = products.map((product) => ({
130
+ // remove default_price when new pricing is ready
131
+ ...util.pick(product, ['id', 'created', 'default_price', 'description', 'name', 'metadata']),
132
+ type: product.name.match(/housing/i) ? 'project' : 'subscription', // overwriting, was 'service'
133
+ prices: prices
134
+ .filter((price) => price.product == product.id)
135
+ .map((price) => ({
136
+ ...util.pick(price, ['id', 'product', 'nickname', 'recurring', 'unit_amount', 'tiers', 'tiers_mode']),
137
+ interval: price.recurring?.interval, // 'year', 'month', undefined
138
+ })),
139
+ })))
140
+ } catch (err) {
141
+ console.error(new Error(err)) // when stripe throws errors, the callstack is missing.
142
+ return []
143
+ }
144
+ }
145
+
146
+ async function getUserFromEvent(event) {
147
+ // User retreived from the event's customer.
148
+ // The customer is created before the paymentIntent and subscriptionIntent is set up
149
+ let object = event.data.object
150
+ let customerId = object.object == 'customer'? object.id : object.customer
151
+ if (customerId) {
152
+ var user = await db.user.findOne({
153
+ query: { 'stripeCustomer.id': customerId },
154
+ populate: db.user.populate({}),
155
+ blacklist: false, // ['-company.users.inviteToken'],
156
+ })
157
+ }
158
+ if (!user) {
159
+ await db.log.insert({ data: {
160
+ date: Date.now(),
161
+ event: event.type,
162
+ message: `Cannot find user with id: ${customerId}.`,
163
+ }})
164
+ throw new Error(`Cannot find user with id: ${customerId}.`)
165
+ }
166
+ // populate company owner with user data (handy for _addSubscriptionBillingChange)
167
+ if (user.company?.users) {
168
+ user.company.users = user.company.users.map(o => {
169
+ if (o.role == 'owner' && o._id.toString() == user._id.toString()) {
170
+ o.firstName = user.firstName
171
+ o.name = user.name
172
+ o.email = user.email
173
+ }
174
+ return o
175
+ })
176
+ }
177
+ return user
178
+ }
108
179
 
109
180
  async function webhookCustomerCreatedUpdated(req, res, event) {
110
181
  try {
@@ -169,72 +240,6 @@ async function webhookSubUpdated(req, res, event) {
169
240
  }
170
241
  }
171
242
 
172
- async function getUserFromEvent(event) {
173
- // User retreived from the event's customer.
174
- // The customer is created before the paymentIntent and subscriptionIntent is set up
175
- let object = event.data.object
176
- let customerId = object.object == 'customer'? object.id : object.customer
177
- if (customerId) {
178
- var user = await db.user.findOne({
179
- query: { 'stripeCustomer.id': customerId },
180
- populate: db.user.populate({}),
181
- blacklist: false, // ['-company.users.inviteToken'],
182
- })
183
- }
184
- if (!user) {
185
- await db.log.insert({ data: {
186
- date: Date.now(),
187
- event: event.type,
188
- message: `Cannot find user with id: ${customerId}.`,
189
- }})
190
- throw new Error(`Cannot find user with id: ${customerId}.`)
191
- }
192
- // populate company owner with user data (handy for _addSubscriptionBillingChange)
193
- if (user.company?.users) {
194
- user.company.users = user.company.users.map(o => {
195
- if (o.role == 'owner' && o._id.toString() == user._id.toString()) {
196
- o.firstName = user.firstName
197
- o.name = user.name
198
- o.email = user.email
199
- }
200
- return o
201
- })
202
- }
203
- return user
204
- }
205
-
206
- export async function getProducts() {
207
- /**
208
- * Returns all products and caches it on the app
209
- * @returns {Array} products
210
- */
211
- try {
212
- if (stripeProducts) return stripeProducts
213
- if (!config.stripeSecretKey) {
214
- stripeProducts = []
215
- throw new Error('Missing process.env.stripeSecretKey for retrieving products')
216
- }
217
-
218
- let products = (await stripe.products.list({ limit: 100, active: true })).data
219
- let prices = (await stripe.prices.list({ limit: 100, active: true, expand: ['data.tiers'] })).data
220
-
221
- return (stripeProducts = products.map((product) => ({
222
- // remove default_price when new pricing is ready
223
- ...util.pick(product, ['id', 'created', 'default_price', 'description', 'name', 'metadata']),
224
- type: product.name.match(/housing/i) ? 'project' : 'subscription', // overwriting, was 'service'
225
- prices: prices
226
- .filter((price) => price.product == product.id)
227
- .map((price) => ({
228
- ...util.pick(price, ['id', 'product', 'nickname', 'recurring', 'unit_amount', 'tiers', 'tiers_mode']),
229
- interval: price.recurring?.interval, // 'year', 'month', undefined
230
- })),
231
- })))
232
- } catch (err) {
233
- console.error(new Error(err)) // when stripe throws errors, the callstack is missing.
234
- return []
235
- }
236
- }
237
-
238
243
  // async function createOrUpdateCustomer(user, paymentMethod=null) {
239
244
  // /**
240
245
  // * Creates or updates a stripe customer and saves it to the user
@@ -261,10 +266,3 @@ export async function getProducts() {
261
266
  // blacklist: ['-stripeCustomer'],
262
267
  // })
263
268
  // }
264
-
265
- async function error(req, res, err) {
266
- if (err && err.response && err.response.body) console.log(err.response.body)
267
- if (util.isString(err) && err.match(/Cannot find company with id/)) {
268
- res.json({ user: 'no company found' })
269
- } else res.error(err)
270
- }
@@ -24,7 +24,8 @@ export function Button({
24
24
  IconLeftEnd,
25
25
  IconRight,
26
26
  IconRightEnd,
27
- children,
27
+ children,
28
+ type='button',
28
29
  ...props
29
30
  }: Button) {
30
31
  // const size = (color.match(/xs|sm|md|lg/)?.[0] || 'md') as 'xs'|'sm'|'md'|'lg'
@@ -37,7 +38,7 @@ export function Button({
37
38
  const colors = {
38
39
  'primary': 'bg-primary hover:bg-primary-hover ring-transparent text-white [&>.loader]:border-white',
39
40
  'secondary': 'bg-secondary hover:bg-secondary-hover ring-transparent text-white [&>.loader]:border-white',
40
- 'black': 'bg-black hover:bg-gray-700 ring-transparent text-white [&>.loader]:border-white',
41
+ 'black': 'bg-black hover:bg-gray-800 ring-transparent text-white [&>.loader]:border-white',
41
42
  'dark': 'bg-gray-800 hover:bg-gray-700 ring-transparent text-white [&>.loader]:border-white',
42
43
  'white': 'bg-white hover:bg-gray-50 ring-gray-300 text-gray-900 [&>.loader]:border-black', // maybe change to text-foreground
43
44
  'clear': 'hover:bg-gray-50 ring-gray-300 text-foreground [&>.loader]:border-foreground !shadow-none',
@@ -62,7 +63,11 @@ export function Button({
62
63
  }
63
64
 
64
65
  return (
65
- <button class={twMerge(`${base} ${sizes[size]} ${appliedColor} ${contentLayout} ${loading} nitro-button ${className||''}`)} {...props}>
66
+ <button
67
+ type={type}
68
+ class={twMerge(`${base} ${sizes[size]} ${appliedColor} ${contentLayout} ${loading} nitro-button ${className||''}`)}
69
+ {...props}
70
+ >
66
71
  {IconLeft && getIcon(IconLeft)}
67
72
  {IconLeftEnd && getIcon(IconLeftEnd)}
68
73
  <span class={`${iconPosition == 'leftEnd' || iconPosition == 'rightEnd' ? 'flex-1' : ''}`}>{children}</span>
@@ -25,8 +25,8 @@ export function Message({ className }: { className?: string }) {
25
25
  'unauth': { type: 'error', text: 'You are unauthorised' },
26
26
  }
27
27
  const colorMap = {
28
- 'error': 'text-critical',
29
- 'warning': 'text-error',
28
+ 'error': 'text-danger',
29
+ 'warning': 'text-warning',
30
30
  'info': 'text-info',
31
31
  'success': 'text-success',
32
32
  }
@@ -70,7 +70,7 @@ export function Message({ className }: { className?: string }) {
70
70
  // Show message and hide it again after some time. Send back cleanup if store.message changes
71
71
  } else if (messageObject && now - 500 < messageObject.date) {
72
72
  const timeout1 = setTimeout(() => setVisible(true), 50)
73
- if (messageObject.timeout !== 0 && !devDontHide) var timeout2 = setTimeout(hide, messageObject.timeout || 5000)
73
+ if (messageObject.timeout !== 0 && !devDontHide) var timeout2 = setTimeout(hide, messageObject.timeout || 5000000)
74
74
  return () => {
75
75
  clearTimeout(timeout1)
76
76
  clearTimeout(timeout2)
@@ -97,24 +97,24 @@ export function Message({ className }: { className?: string }) {
97
97
  (visible ? 'translate-x-0 opacity-100' : 'translate-x-1 opacity-0')
98
98
  }>
99
99
  <div className="p-3">
100
- <div className="flex items-start">
101
- <div className="shrink-0">
102
- <CircleCheck aria-hidden="true" size={21} className={`${color} mt-0.5`} />
100
+ <div className="flex items-start gap-3 text-sm leading-[1.4em]">
101
+ <div className="flex items-center shrink-0 min-h-[1.4em]">
102
+ <CircleCheck aria-hidden="true" size={19} className={`${color}`} />
103
103
  </div>
104
- <div className="ml-3 flex-1 pt-0.5">
105
- <p className="text-sm font-medium text-gray-900">{typeof store.message === 'object' && store.message?.text}
104
+ <div className="flex flex-1 items-center min-h-[1.4em]">
105
+ <p className="font-medium text-gray-900">{typeof store.message === 'object' && store.message?.text}
106
106
  </p>
107
107
  {/* <p className="mt-1 text-sm text-gray-500">{store.message.text}</p> */}
108
108
  </div>
109
- <div className="ml-4 flex shrink-0">
109
+ <div className="flex items-center shrink-0 min-h-[1.4em]">
110
110
  <button
111
111
  type="button"
112
112
  onClick={hide}
113
- className="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2
113
+ className="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2
114
114
  focus:ring-indigo-500 focus:ring-offset-2"
115
115
  >
116
116
  <span className="sr-only">Close</span>
117
- <X aria-hidden="true" size={19} className="mt-0.5" />
117
+ <X aria-hidden="true" size={19} />
118
118
  </button>
119
119
  </div>
120
120
  </div>
@@ -5,13 +5,15 @@ type ModalProps = {
5
5
  show: boolean
6
6
  setShow: (show: boolean) => void
7
7
  children: React.ReactNode
8
+ className?: string
9
+ rootClassName?: string
10
+ dismissable?: boolean
8
11
  maxWidth?: string
9
12
  minHeight?: string
10
- dismissable?: boolean
11
13
  [key: string]: unknown
12
14
  }
13
15
 
14
- export function Modal({ show, setShow, children, maxWidth, minHeight, dismissable = true, ...props }: ModalProps) {
16
+ export function Modal({ show, setShow, children, maxWidth, minHeight, dismissable = true, className, rootClassName }: ModalProps) {
15
17
  const [state, setState] = useState(show ? 'open' : 'close')
16
18
  const containerEl = useRef<HTMLDivElement>(null)
17
19
  const isFirst = IsFirstRender()
@@ -33,8 +35,7 @@ export function Modal({ show, setShow, children, maxWidth, minHeight, dismissabl
33
35
  container: 'opacity-100 scale-[1] duration-200',
34
36
  },
35
37
  }
36
- const _state = states[state as keyof typeof states]
37
-
38
+ const stateObj = states[state as keyof typeof states]
38
39
 
39
40
  useEffect(() => {
40
41
  if (isFirst) return
@@ -64,15 +65,15 @@ export function Modal({ show, setShow, children, maxWidth, minHeight, dismissabl
64
65
  return (
65
66
  <div
66
67
  onClick={(e) => e.stopPropagation()}
67
- class={`${twMerge(`fixed top-0 w-[100vw] h-[100vh] z-[100] ${_state.root} ${props.className}`)} nitro-modal`}
68
+ class={`${twMerge(`fixed top-0 w-[100vw] h-[100vh] z-[100] ${stateObj.root} ${rootClassName||''}`)} nitro-modal`}
68
69
  >
69
- <div class={`!absolute inset-0 box-content bg-gray-500/70 transition-opacity ${_state.bg}`}></div>
70
- <div class={`relative h-[100vh] overflow-y-auto transition-[opacity,transform] ${_state.container}`}>
70
+ <div class={`!absolute inset-0 box-content bg-gray-500/70 transition-opacity ${stateObj.bg}`}></div>
71
+ <div class={`relative h-[100vh] overflow-y-auto transition-[opacity,transform] ${stateObj.container}`}>
71
72
  <div class="flex items-center justify-center min-h-full" onMouseDown={onClick}>
72
73
  <div
73
74
  ref={containerEl}
74
75
  style={{ maxWidth: maxWidth || '550px', minHeight: minHeight }}
75
- class={`relative w-full mx-6 mt-4 mb-8 bg-white rounded-lg shadow-lg ${props.className}`}
76
+ class={`relative w-full mx-6 mt-4 mb-8 bg-white rounded-lg shadow-lg p-9 ${className||''}`}
76
77
  >
77
78
  <div
78
79
  class="absolute top-0 right-0 p-3 m-1 cursor-pointer"
@@ -133,7 +133,7 @@ function FieldContainer({ children, className, error }: { children: React.ReactN
133
133
  return (
134
134
  <div css={style} className={twMerge(`mt-2.5 mb-6 mt-input-before mb-input-after grid grid-cols-1 nitro-field ${className || ''}`)}>
135
135
  {children}
136
- {error && <div class="mt-1.5 text-xs text-danger nitro-error">{error.detail}</div>}
136
+ {error && <div class="mt-1.5 text-xs text-danger-foreground nitro-error">{error.detail}</div>}
137
137
  </div>
138
138
  )
139
139
  }
@@ -149,7 +149,7 @@ function getInputClasses({ error, Icon, iconPos, type }: { error?: Error, Icon?:
149
149
  'placeholder:text-input-placeholder focus:outline focus:outline-2 focus:-outline-offset-2 ' +
150
150
  (iconPos == 'right' && Icon ? `${pl} ${prWithIcon} ` : (Icon ? `${plWithIcon} ${pr} ` : `${pl} ${pr} `)) +
151
151
  (error
152
- ? 'text-red-900 outline-danger focus:outline-danger '
152
+ ? 'text-danger-foreground outline-danger focus:outline-danger '
153
153
  : 'text-input outline-input-border focus:outline-input-border-focus ') +
154
154
  (iconPos == 'right' ? 'justify-self-start ' : 'justify-self-end ') +
155
155
  'nitro-input'
@@ -18,7 +18,7 @@ export function FormError({ state, fields, className }: FormError) {
18
18
  return (
19
19
  <>
20
20
  {error ? (
21
- <div class={`text-danger mt-1 text-sm nitro-error ${className||''}`}>
21
+ <div class={`text-danger-foreground mt-1 text-sm nitro-error ${className||''}`}>
22
22
  {error.detail}
23
23
  </div>
24
24
  ) : null}
@@ -151,7 +151,7 @@ function SelectBase({ inputId, minMenuWidth, name, prefix='', onChange, options,
151
151
  // isDisabled={true}
152
152
  // maxMenuHeight={200}
153
153
  />
154
- {error && <div class="mt-1.5 text-xs text-danger">{error.detail}</div>}
154
+ {error && <div class="mt-1.5 text-xs text-danger-foreground">{error.detail}</div>}
155
155
  </div>
156
156
  )
157
157
  }
@@ -257,7 +257,7 @@ const selectStyles = {
257
257
  // Input container objects
258
258
  input: {
259
259
  base: 'text-input',
260
- error: 'text-red-900',
260
+ error: 'text-danger-foreground',
261
261
  },
262
262
  multiValue: 'bg-primary text-white rounded items-center pl-2 pr-1.5 gap-1.5',
263
263
  multiValueLabel: 'text-xs',
@@ -265,10 +265,10 @@ const selectStyles = {
265
265
  placeholder: 'text-input-placeholder',
266
266
  singleValue: {
267
267
  base: 'text-input',
268
- error: 'text-red-900',
268
+ error: 'text-danger-foreground',
269
269
  },
270
270
  // Icon indicators
271
- clearIndicator: 'text-gray-500 p-1 rounded-md hover:bg-red-50 hover:text-red-800',
271
+ clearIndicator: 'text-gray-500 p-1 rounded-md hover:bg-red-50 hover:text-danger-foreground',
272
272
  dropdownIndicator: 'p-1 hover:bg-gray-100 text-gray-500 rounded-md hover:text-black',
273
273
  indicatorsContainer: 'p-1 px-2 gap-1',
274
274
  indicatorSeparator: 'py-0.5 before:content-[""] before:block before:bg-gray-100 before:w-px before:h-full',
@@ -279,7 +279,7 @@ const selectStyles = {
279
279
  option: {
280
280
  base: 'relative px-3 py-2 !flex items-center gap-2 cursor-default',
281
281
  hover: 'bg-gray-50',
282
- selected: '!bg-gray-100 text-primary-dark',
282
+ selected: '!bg-gray-100 text-dropdown-selected-foreground',
283
283
  },
284
284
  }
285
285
 
@@ -348,7 +348,7 @@ export function Styleguide({ className, elements, children }: StyleguideProps) {
348
348
  </div>
349
349
  </div>
350
350
 
351
- <Modal show={showModal1} setShow={setShowModal1} class="p-9">
351
+ <Modal show={showModal1} setShow={setShowModal1}>
352
352
  <h3 class="h3">Edit Profile</h3>
353
353
  <p class="mb-5">An example modal containing a basic form for editing profiles.</p>
354
354
  <form class="mb-8 text-left">
@@ -1,13 +1,9 @@
1
1
  // @ts-nocheck
2
2
  import db from 'monastery'
3
3
 
4
- export default {
5
- routes: {
6
- 'put /api/company/:cid': ['isCompanyUser', 'update'],
7
- 'put /api/user/:uid': ['isUser', 'updateUser'],
8
- },
9
- update: update,
10
- updateUser: updateUser,
4
+ export const routes = {
5
+ 'put /api/company/:cid': ['isCompanyUser', update],
6
+ 'put /api/user/:uid': ['isUser', updateUser],
11
7
  }
12
8
 
13
9
  async function update(req, res) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-web",
3
- "version": "0.0.62",
3
+ "version": "0.0.64",
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 🚀",
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "peerDependencies": {
57
57
  "@stripe/stripe-js": "^1.34.0",
58
- "monastery": "~3.5.5",
58
+ "monastery": "~3.5.6",
59
59
  "stripe": "^9.16.0"
60
60
  },
61
61
  "_peers-are-packages-that-will-be-used-in-the-host-repo-too": "",
@@ -0,0 +1,23 @@
1
+ <!-- extends references the default nitro layout file -->
2
+ {% extends "partials/layout1.swig" %}
3
+
4
+ <!-- block content is the content of the email -->
5
+ {% block content %}
6
+
7
+ [[ subject = You've been invited to join IMG ]]
8
+ <b>%recipient.greet%</b>,<br/>
9
+ <br/>
10
+ You've been invited to join IMG.<br/>
11
+ <br/>
12
+ If you didn't expect to receive this email, feel free to disregard this email.<br/>
13
+ <br/>
14
+ <span mc:edit="button">
15
+ <a class="button" href="%recipient.domain%/invite/%recipient.token%" target="_blank">
16
+ Accept Invite
17
+ </a>
18
+ </span><br/>
19
+ <br/>
20
+ Thanks,<br/>
21
+ <b>The %recipient.configName% Team</b>
22
+
23
+ {% endblock %}
package/server/index.js CHANGED
@@ -18,16 +18,12 @@ export { setupRouter } from './router.js'
18
18
  // Export email utility
19
19
  export { sendEmail } from './email/index.js'
20
20
 
21
- // Export api default controllers
22
- export {
23
- default as auth,
24
- findUserFromProvider,
25
- signinAndGetState,
26
- userCreate,
27
- tokenCreate,
28
- tokenParse,
29
- validatePassword,
30
- getStore,
31
- } from '../components/auth/auth.api.js'
32
- export { default as settings } from '../components/settings/settings.api.js'
33
- export { default as stripe } from '../components/billing/stripe.api.js'
21
+ // Export API controllers
22
+ export * from '../components/auth/auth.api.js'
23
+ export * from '../components/settings/settings.api.js'
24
+ export * from '../components/billing/stripe.api.js'
25
+
26
+ export { routes as authRoutes } from '../components/auth/auth.api.js'
27
+ export { routes as settingsRoutes } from '../components/settings/settings.api.js'
28
+ export { routes as stripeRoutes } from '../components/billing/stripe.api.js'
29
+
package/server/router.js CHANGED
@@ -20,8 +20,9 @@ export async function setupRouter (config) {
20
20
  const expressApp = express()
21
21
  const server = http.createServer(expressApp)
22
22
  const apiRoutes = {}
23
- const controllers = {}
23
+ const controllers = {} // { controllerName: { ...routes, ...helpers } }
24
24
  const allMiddleware = { ...defaultMiddleware, ...(middleware || {}) }
25
+ const verbs = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'all']
25
26
 
26
27
  if (!env) {
27
28
  throw new Error('setupRouter: `config.env` missing')
@@ -40,23 +41,27 @@ export async function setupRouter (config) {
40
41
  let filepaths = getFiles(componentsDir, /\.api\.js$/)
41
42
  // console.log(filepaths, componentsDir)
42
43
  for (let filepath of filepaths) {
43
- let file = (await import(filepath)).default
44
- let name = filepath.replace(/^.*[\\\/]|\.api\.js$/g, '') // eslint-disable-line
45
- controllers[name] = file
44
+ const file = await import(filepath)
45
+ const routes = file.routes
46
+ const name = filepath.replace(/^.*[\\\/]|\.api\.js$/g, '') // eslint-disable-line
47
+ controllers[name] = routes // { ...routes, ...helpers }
46
48
 
47
- if (!file) {
48
- console.warn(`API warning: no default export found on file ${filepath}`)
49
+ if (!routes) {
50
+ console.warn(`API warning: no 'routes' export found on file ${filepath}`)
49
51
  continue
50
52
  }
51
53
 
52
- if (file.setup) file.setup(allMiddleware, config)
53
- if (file.routes) {
54
- util.each(file.routes, (_middleware, key) => {
54
+ if (routes?.setup) routes.setup.call(routes, allMiddleware, config)
55
+ if (routes) {
56
+ util.each(routes, (_middleware, key) => {
57
+ if (!key.match(/\s/)) return
58
+ const match = key.match(new RegExp(`^(${verbs.join('|')})\\s+(.*)$`, 'i'))
59
+ if (!match) throw new Error(`Invalid verb or path: ${key}`)
55
60
  apiRoutes[key] = {
56
61
  middleware: util.toArray(_middleware),
57
62
  filename: name,
58
- path: key.replace(/^[a-z]+\s+/, ''),
59
- verb: key.match(/^[a-z]+/)? key.match(/^[a-z]+/)[0] : 'get',
63
+ path: match[2],
64
+ verb: match[1],
60
65
  }
61
66
  })
62
67
  }
@@ -265,39 +270,23 @@ function getFiles (dir, regexp) {
265
270
  return paths
266
271
  }
267
272
 
268
- function resolveMiddleware (controllers, middleware, route, item, last) {
273
+ function resolveMiddleware (controllers, middleware, route, item) {
269
274
  /**
270
275
  * Resolves a placeholder string into a function
276
+ * @param {object} controllers - { controllerName: { routes, setup, ...other exported functions } }
277
+ * @param {object} middleware
271
278
  * @param {object} route
272
279
  * @param {fn|string} item
273
280
  * @param {boolean} last - last item
274
281
  * @return function(req, res){..}
275
282
  */
276
283
  if (util.isFunction(item)) {
277
- return item
284
+ return item.bind(controllers[route.filename])
278
285
 
279
- } else if (!util.isString(item)) {
280
- console.error('Invalid middleware item:', item)
286
+ } else if (typeof item !== 'string') {
287
+ console.error('Invalid middleware item on route:', route.path, item)
281
288
  return
282
289
 
283
- } else if (item.match(/\./) || last) { // e.g. user.read
284
- let arr = item.split('.')
285
- let controllerGroup = controllers[arr[1]? arr[0] : route.filename]
286
- let controllerName = arr[1] || arr[0]
287
- if (controllerGroup && controllerGroup[controllerName]) {
288
- if (middleware.endpointSwitcher && controllerGroup[controllerName + 'Desktop']) {
289
- return middleware.endpointSwitcher.bind(
290
- null,
291
- controllerGroup[controllerName].bind(controllerGroup),
292
- controllerGroup[controllerName + 'Desktop'].bind(controllerGroup)
293
- )
294
- } else {
295
- return controllerGroup[controllerName].bind(controllerGroup)
296
- }
297
- } else {
298
- console.error(`The controller '${item}' defined in '${route.filename}.api' doesn't exist.`)
299
- }
300
-
301
290
  } else if (middleware[item]) {
302
291
  return middleware[item]
303
292
 
package/types/util.d.ts CHANGED
@@ -583,13 +583,14 @@ export function s3Image(awsUrl: string, imageOrArray: Image[] | Image, size?: st
583
583
  export function sanitizeHTML(string: string): string;
584
584
  /**
585
585
  * Process scrollbar width once.
586
- * @param {string} paddingClass - class name to give padding to
587
- * @param {string} marginClass - class name to give margin to
588
- * @param {number} maxWidth - enclose css in a max-width media query
586
+ * @param {string} [paddingClass] - class name to give padding to
587
+ * @param {string} [marginClass] - class name to give margin to
588
+ * @param {number} [maxWidth] - enclose css in a max-width media query
589
+ * @param {string} [marginClassNegative] - class name to give negative margin to
589
590
  * @returns {number}
590
591
  *
591
592
  */
592
- export function scrollbar(paddingClass: string, marginClass: string, maxWidth: number): number;
593
+ export function scrollbar(paddingClass?: string, marginClass?: string, marginClassNegative?: string, maxWidth?: number): number;
593
594
  /**
594
595
  * Convert seconds to time
595
596
  * @param {number} seconds
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../util.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../util.js"],"names":[],"mappings":"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"}
package/util.js CHANGED
@@ -1410,13 +1410,14 @@ export function sanitizeHTML (string) {
1410
1410
 
1411
1411
  /**
1412
1412
  * Process scrollbar width once.
1413
- * @param {string} paddingClass - class name to give padding to
1414
- * @param {string} marginClass - class name to give margin to
1415
- * @param {number} maxWidth - enclose css in a max-width media query
1413
+ * @param {string} [paddingClass] - class name to give padding to
1414
+ * @param {string} [marginClass] - class name to give margin to
1415
+ * @param {number} [maxWidth] - enclose css in a max-width media query
1416
+ * @param {string} [marginClassNegative] - class name to give negative margin to
1416
1417
  * @returns {number}
1417
1418
  *
1418
1419
  */
1419
- export function scrollbar (paddingClass, marginClass, maxWidth) {
1420
+ export function scrollbar (paddingClass, marginClass, marginClassNegative, maxWidth) {
1420
1421
  if (typeof window === 'undefined') return 0
1421
1422
  if (scrollbarCache || scrollbarCache === 0) {
1422
1423
  return scrollbarCache
@@ -1452,6 +1453,7 @@ export function scrollbar (paddingClass, marginClass, maxWidth) {
1452
1453
  (maxWidth ? '@media only screen and (max-width: ' + maxWidth + 'px) {' : '') +
1453
1454
  (paddingClass ? paddingClass + ' {padding-right:' + scrollbarCache + 'px}' : '') +
1454
1455
  (marginClass ? marginClass + ' {margin-right:' + scrollbarCache + 'px}' : '') +
1456
+ (marginClassNegative ? marginClassNegative + ' {margin-right:' + scrollbarCache * -1 + 'px}' : '') +
1455
1457
  (maxWidth ? '}' : '')
1456
1458
  document.head.appendChild(style)
1457
1459
  }