nitro-web 0.0.26 → 0.0.27

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/client/app.tsx CHANGED
@@ -5,6 +5,7 @@ import { AxiosRequestConfig } from '@hokify/axios'
5
5
  import { beforeCreate, Provider, exposedData } from './store'
6
6
  import { axios, camelCase, pick, toArray, setTimeoutPromise } from 'nitro-web/util'
7
7
  import { Config, Store } from 'nitro-web/types'
8
+ import { injectedConfig } from './index'
8
9
 
9
10
  type LayoutProps = {
10
11
  config: Config;
@@ -22,7 +23,7 @@ type Settings = {
22
23
  }
23
24
 
24
25
  type Route = {
25
- component: React.FC<{ route?: Route; params?: object; location?: object }>
26
+ component: React.FC<{ route?: Route; params?: object; location?: object; config?: Config }>
26
27
  meta?: { title?: string }
27
28
  middleware: string[]
28
29
  name: string
@@ -41,6 +42,10 @@ export async function setupApp(config: Config, layouts: React.FC<LayoutProps>[])
41
42
  name: config.name,
42
43
  titleSeparator: config.titleSeparator,
43
44
  }
45
+
46
+ // Setup the jwt token
47
+ updateJwt(localStorage.getItem(injectedConfig.jwtName))
48
+
44
49
  if (!settings.layouts) throw new Error('layouts are required')
45
50
  const initData = (await settings.beforeApp(config)) || {}
46
51
  beforeCreate(initData, settings.beforeStoreUpdate)
@@ -49,6 +54,14 @@ export async function setupApp(config: Config, layouts: React.FC<LayoutProps>[])
49
54
  root.render(<App settings={settings} config={config} />)
50
55
  }
51
56
 
57
+ export function updateJwt(token?: string | null) {
58
+ // Update the jwt token in local storage and axios headers
59
+ const key = injectedConfig.jwtName
60
+ localStorage.setItem(key, token || '')
61
+ if (token) axios().defaults.headers.Authorization = `Bearer ${token}`
62
+ else delete axios().defaults.headers.Authorization
63
+ }
64
+
52
65
  function App({ settings, config }: { settings: Settings, config: Config }): ReactNode {
53
66
  // const themeNormalised = theme
54
67
  const router = getRouter({ settings, config })
@@ -182,7 +195,7 @@ function getRouter({ settings, config }: { settings: Settings, config: Config })
182
195
  children: layout.map((route) => {
183
196
  return {
184
197
  element: (
185
- <RouteComponent route={route} />
198
+ <RouteComponent route={route} config={config} />
186
199
  ),
187
200
  path: route.path,
188
201
  loader: async () => { // request
@@ -230,13 +243,13 @@ function RestoreScroll() {
230
243
  return (null)
231
244
  }
232
245
 
233
- function RouteComponent({ route }: { route: Route }) {
246
+ function RouteComponent({ route, config }: { route: Route, config: Config }) {
234
247
  const Component = route.component
235
248
  const params = useParams()
236
249
  const location = useLocation()
237
250
  document.title = route.meta?.title || ''
238
251
  return (
239
- <Component route={route} params={params} location={location} />
252
+ <Component route={route} params={params} location={location} config={config} />
240
253
  )
241
254
  }
242
255
 
@@ -280,6 +293,13 @@ function beforeStoreUpdate(prevStore: Store | null, newData: Store) {
280
293
  * @return {object} store
281
294
  */
282
295
  if (!newData) return newData
296
+
297
+ // If newData.jwt is present, update the jwt token
298
+ if (newData.jwt) {
299
+ updateJwt(newData.jwt)
300
+ delete newData.jwt
301
+ }
302
+
283
303
  const store = {
284
304
  ...(prevStore || {
285
305
  message: undefined,
@@ -288,9 +308,8 @@ function beforeStoreUpdate(prevStore: Store | null, newData: Store) {
288
308
  ...(newData || {}),
289
309
  }
290
310
 
291
- // Used to verify if the current cookie belongs to this user
292
- // E.g. signout > signin (to a different user on another tab)
293
- axios().defaults.headers.userid = store?.user?._id
311
+ // E.g. Cookie matching handy for rare issues, e.g. signout > signin (to a different user on another tab)
312
+ axios().defaults.headers.authid = store?.user?._id
294
313
  return store
295
314
  }
296
315
 
package/client/index.ts CHANGED
@@ -48,5 +48,5 @@ export { Toggle } from '../components/partials/form/toggle'
48
48
  // Component Other
49
49
  export { IsFirstRender } from '../components/partials/is-first-render'
50
50
 
51
- // IsDemo environment variable
52
- export const isDemo = ISDEMO
51
+ // Expose the injected config
52
+ export const injectedConfig = { ...INJECTED_CONFIG } as import('types').Config
package/client/store.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { createContainer } from 'react-tracked'
2
2
  import { Store } from 'nitro-web/types'
3
3
 
4
-
5
4
  export type BeforeUpdate = (prevStore: Store | null, newData: Store) => Store
6
5
 
7
6
  let initData: Store
@@ -1,15 +1,15 @@
1
1
  // @ts-nocheck
2
- import MongoStore from 'connect-mongo'
3
2
  import crypto from 'crypto'
4
- import expressSession from 'express-session'
5
3
  import passport from 'passport'
6
4
  import passportLocal from 'passport-local'
5
+ import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt'
7
6
  import db from 'monastery'
7
+ import jsonwebtoken from 'jsonwebtoken'
8
8
  import { sendEmail } from 'nitro-web/server'
9
9
  import * as util from 'nitro-web/util'
10
- // import stripeController from '../billing/stripe.api.js'
11
10
 
12
11
  let config = {}
12
+ const JWT_SECRET = process.env.JWT_SECRET || 'replace_this_with_secure_env_secret'
13
13
 
14
14
  export default {
15
15
 
@@ -24,43 +24,19 @@ export default {
24
24
  },
25
25
 
26
26
  setup: function (middleware, _config) {
27
- // Setup passport handlers for reading and writing to req.session
28
27
  const that = this
29
28
  global.passport = passport
30
29
 
31
30
  // Set config values
32
- config = {
33
- env: _config.env,
34
- masterPassword: _config.masterPassword,
35
- }
36
- for (let key in config) {
37
- if (!config[key] && key != 'masterPassword') {
38
- throw new Error(`Missing config value for stripe.api.js: ${key}`)
39
- }
40
- }
41
-
42
- // After successful login, serialize the user into a session object
43
- passport.serializeUser((user, next) => {
44
- next(null, { _id: user._id })
45
- })
31
+ config = { env: _config.env, masterPassword: _config.masterPassword }
32
+ if (!config.env) throw new Error('Missing config value for: config.env')
46
33
 
47
- // After session read, get the user from the session object
48
- passport.deserializeUser(async (sessionObject, next) => {
49
- try {
50
- const user = await that._findUserFromProvider('deserialize', sessionObject)
51
- next(null, user)
52
- } catch (err) {
53
- next(err.message)
54
- }
55
- })
56
-
57
- // Setup passport local signin strategy
58
34
  passport.use(
59
35
  new passportLocal.Strategy(
60
- { usernameField: 'email' },
36
+ { usernameField: 'email' },
61
37
  async (email, password, next) => {
62
38
  try {
63
- const user = await that._findUserFromProvider('email', { email: email, password: password })
39
+ const user = await that._findUserFromProvider('email', { email, password })
64
40
  next(null, user)
65
41
  } catch (err) {
66
42
  next(err.message)
@@ -69,58 +45,45 @@ export default {
69
45
  )
70
46
  )
71
47
 
72
- // https://medium.com/swlh/everything-you-need-to-know-about-the-passport-jwt-passport-js-strategy-8b69f39014b0
73
- // https://github.com/mikenicholson/passport-jwt
74
- //
75
- // passport.use(new JwtStrategy.Strategy({
76
- // jwtFromRequest: JwtStrategy.ExtractJwt.fromAuthHeaderAsBearerToken(),
77
- // secretOrKey: '1fjw3h3jkdJD8sjA12dw53llapA2sjAjsv3nxaxzNBzz',
78
- // }, function(jwtPayload, done) {
79
- //
80
- // this._findUserFromProvider('email', { email: email, password: password }, done)
81
- // console.log(jwtPayload)
82
- // User.findOne({id: jwt_payload.sub}, function(err, user) {
83
- // if (err) {
84
- // return done(err, false);
85
- // }
86
- // if (user) {
87
- // return done(null, user);
88
- // } else {
89
- // return done(null, false);
90
- // // or you could create a new account
91
- // }
92
- // });
93
- // }));
94
-
95
- // Add session middleware
96
- middleware.order.splice(3, 0, 'session', 'passport', 'passportSession', 'passportError', 'blocked')
48
+ passport.use(
49
+ new JwtStrategy(
50
+ {
51
+ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
52
+ secretOrKey: JWT_SECRET,
53
+ },
54
+ async (payload, done) => {
55
+ try {
56
+ const user = await that._findUserFromProvider('deserialize', { _id: payload._id })
57
+ if (!user) return done(null, false)
58
+ return done(null, user)
59
+ } catch (err) {
60
+ return done(err, false)
61
+ }
62
+ }
63
+ )
64
+ )
65
+
66
+ middleware.order.splice(3, 0, 'passport', 'passportError', 'jwtAuth', 'blocked')
67
+
97
68
  Object.assign(middleware, {
98
69
  blocked: function (req, res, next) {
99
70
  if (req.user && req.user.loginActive === false) {
100
- req.logout()
101
- res.error('This user is not available.')
71
+ res.status(403).error('This user is not available.')
102
72
  } else {
103
73
  next()
104
74
  }
105
75
  },
76
+ jwtAuth: function(req, res, next) {
77
+ passport.authenticate('jwt', { session: false }, function(err, user) {
78
+ if (user) req.user = user
79
+ next()
80
+ })(req, res, next)
81
+ },
106
82
  passport: passport.initialize(),
107
83
  passportError: function (err, req, res, next) {
108
84
  if (!err) return next()
109
- req.logout()
110
85
  res.error(err)
111
86
  },
112
- passportSession: passport.session(),
113
- session: expressSession({
114
- secret: '092720e5ffc1237266b8517239cd81b6', // Changing invalidates cookies
115
- cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 },
116
- resave: false,
117
- saveUninitialized: false,
118
- store: MongoStore.create({
119
- clientPromise: db.onOpen((manager) => {
120
- return manager.client
121
- }),
122
- }),
123
- }),
124
87
  })
125
88
  },
126
89
 
@@ -131,31 +94,26 @@ export default {
131
94
  signup: async function (req, res) {
132
95
  try {
133
96
  let user = await this._userCreate(req.body)
134
- // Welcome email
135
97
  sendEmail({
136
98
  config: config,
137
99
  template: 'welcome',
138
100
  to: `${util.ucFirst(user.firstName)}<${user.email}>`,
139
- }).catch(err => {
140
- console.error(err)
141
- })
142
- // Login
143
- res.send(await this._signinAndGetState(req, user))
101
+ }).catch(console.error)
102
+ res.send(await this._signinAndGetState(user, req.query.desktop))
144
103
  } catch (err) {
145
104
  res.error(err)
146
105
  }
147
106
  },
148
107
 
149
108
  signin: function (req, res) {
150
- // console.log('api: signin')
151
- // console.log(req.body)
152
109
  if (!req.body.email) return res.error('email', 'The email you entered is incorrect.')
153
110
  if (!req.body.password) return res.error('password', 'The password you entered is incorrect.')
111
+
154
112
  passport.authenticate('local', { session: false }, async (err, user, info) => {
155
- if (err) return console.log(err) || res.error(err)
113
+ if (err) return res.error(err)
156
114
  if (!user && info) return res.error('email', info.message)
157
115
  try {
158
- const response = await this._signinAndGetState(req, user)
116
+ const response = await this._signinAndGetState(user, req.query.desktop)
159
117
  res.send(response)
160
118
  } catch (err) {
161
119
  res.error(err)
@@ -164,35 +122,29 @@ export default {
164
122
  },
165
123
 
166
124
  signout: function (req, res) {
167
- req.logout()
168
125
  res.json('{}')
169
126
  },
170
127
 
171
128
  resetInstructions: async function (req, res) {
172
129
  try {
173
- let email = (req.body.email||'').trim().toLowerCase()
174
- if (!email || !util.isString(email)) {
175
- throw { title: 'email', detail: 'The email you entered is incorrect.' }
176
- }
177
- // Find matching user and create new reset token
178
- let user = await db.user.findOne({ query: { email: email }, _privateData: true })
130
+ let email = (req.body.email || '').trim().toLowerCase()
131
+ if (!email || !util.isString(email)) throw { title: 'email', detail: 'The email you entered is incorrect.' }
132
+
133
+ let user = await db.user.findOne({ query: { email }, _privateData: true })
179
134
  if (!user) throw { title: 'email', detail: 'The email you entered is incorrect.' }
180
- // Create token
181
- let token = await this._tokenCreate(user._id)
182
- // Update user with token
183
- await db.user.update({ query: { email: email }, $set: { resetToken: token }})
184
- // Email.
135
+
136
+ let resetToken = await this._tokenCreate(user._id)
137
+ await db.user.update({ query: { email }, $set: { resetToken }})
138
+
185
139
  res.json({})
186
140
  sendEmail({
187
141
  config: config,
188
142
  template: 'reset-password',
189
143
  to: `${util.ucFirst(user.firstName)}<${email}>`,
190
144
  data: {
191
- token: token + (req.query.hasOwnProperty('desktop') ? '?desktop' : ''),
145
+ token: resetToken + (req.query.hasOwnProperty('desktop') ? '?desktop' : ''),
192
146
  },
193
- }).catch(err => {
194
- console.error('sendEmail(..) mailgun error', err)
195
- })
147
+ }).catch(err => console.error('sendEmail(..) mailgun error', err))
196
148
  } catch (err) {
197
149
  res.error(err)
198
150
  }
@@ -202,18 +154,11 @@ export default {
202
154
  try {
203
155
  const { token, password, password2 } = req.body
204
156
  const id = this._tokenParse(token)
205
- // Validate password
206
157
  this._validatePassword(password, password2)
207
- // Find matching user
208
- let user = await db.user.findOne({
209
- query: id,
210
- blacklist: ['-resetToken'],
211
- _privateData: true,
212
- })
213
- if (!user || user.resetToken !== token) {
214
- throw new Error('Sorry your email token is invalid or has already been used verify your email.')
215
- }
216
- // Update user with new password
158
+
159
+ let user = await db.user.findOne({ query: id, blacklist: ['-resetToken'], _privateData: true })
160
+ if (!user || user.resetToken !== token) throw new Error('Sorry your email token is invalid or has already been used.')
161
+
217
162
  await db.user.update({
218
163
  query: user._id,
219
164
  data: {
@@ -222,7 +167,7 @@ export default {
222
167
  },
223
168
  blacklist: ['-resetToken', '-password'],
224
169
  })
225
- res.send(await this._signinAndGetState(req, { ...user, resetToken: undefined }))
170
+ res.send(await this._signinAndGetState({ ...user, resetToken: undefined }, req.query.desktop))
226
171
  } catch (err) {
227
172
  res.error(err)
228
173
  }
@@ -264,27 +209,19 @@ export default {
264
209
  /* ---- Private fns ---------------- */
265
210
 
266
211
  _getState: async function (user) {
267
- // Format the initial state
268
- return {
212
+ // Initial state
213
+ return {
269
214
  user: user || null,
270
- // stripeProducts: await stripeController._getProducts(),
271
215
  }
272
216
  },
273
217
 
274
- _signinAndGetState: function (req, user) {
275
- // @return state
276
- return new Promise((resolve, reject) => {
277
- user.desktop = req.query.hasOwnProperty('desktop')
278
- if (user.loginActive !== false) {
279
- req.login(user, async (err) => {
280
- if (err) return reject(err)
281
- resolve(await this._getState(user))
282
- })
283
- } else {
284
- return reject('This user is not available.')
285
- // this._getState().then((state) => resolve(state))
286
- }
287
- })
218
+ _signinAndGetState: async function (user, isDesktop) {
219
+ if (user.loginActive === false) throw 'This user is not available.'
220
+ user.desktop = isDesktop
221
+
222
+ const jwt = jsonwebtoken.sign({ _id: user._id }, JWT_SECRET, { expiresIn: '30d' })
223
+ const state = await this._getState(user)
224
+ return { ...state, jwt }
288
225
  },
289
226
 
290
227
  _tokenCreate: function (id) {
@@ -1,15 +1,15 @@
1
- import { Topbar, Field, Button, FormError, util } from 'nitro-web'
2
- import { Config, Errors } from 'nitro-web/types'
1
+ import { Topbar, Field, Button, FormError, util, injectedConfig, updateJwt } from 'nitro-web'
2
+ import { Errors } from 'nitro-web/types'
3
3
 
4
- export function Signin({ config }: { config: Config }) {
4
+ export function Signin() {
5
5
  const navigate = useNavigate()
6
6
  const location = useLocation()
7
7
  const isSignout = location.pathname == '/signout'
8
8
  const isLoading = useState(isSignout ? 'is-loading' : '')
9
9
  const [, setStore] = useTracked()
10
10
  const [state, setState] = useState({
11
- email: config.env == 'development' ? config.placeholderEmail : '',
12
- password: config.env == 'development' ? '1234' : '',
11
+ email: injectedConfig.env == 'development' ? (injectedConfig.placeholderEmail || '') : '',
12
+ password: injectedConfig.env == 'development' ? '1234' : '',
13
13
  errors: [] as Errors,
14
14
  })
15
15
 
@@ -22,8 +22,10 @@ export function Signin({ config }: { config: Config }) {
22
22
  useEffect(() => {
23
23
  if (isSignout) {
24
24
  setStore(() => ({ user: null }))
25
- util.axios().get('/api/signout')
25
+ // util.axios().get('/api/signout')
26
+ Promise.resolve()
26
27
  .then(() => isLoading[1](''))
28
+ .then(() => updateJwt())
27
29
  .then(() => navigate({ pathname: '/signin', search: location.search }, { replace: true }))
28
30
  .catch(err => (console.error(err), isLoading[1]('')))
29
31
  }
@@ -1,15 +1,15 @@
1
- import { Button, Field, FormError, Topbar, util } from 'nitro-web'
2
- import { Config, Errors } from 'nitro-web/types'
1
+ import { Button, Field, FormError, Topbar, util, injectedConfig } from 'nitro-web'
2
+ import { Errors } from 'nitro-web/types'
3
3
 
4
- export function Signup({ config }: { config: Config}) {
4
+ export function Signup() {
5
5
  const navigate = useNavigate()
6
6
  const isLoading = useState('')
7
7
  const [, setStore] = useTracked()
8
8
  const [state, setState] = useState({
9
- email: config.env === 'development' ? config.placeholderEmail : '',
10
- name: config.env === 'development' ? 'Bruce Wayne' : '',
11
- business: { name: config.env === 'development' ? 'Wayne Enterprises' : '' },
12
- password: config.env === 'development' ? '1234' : '',
9
+ email: injectedConfig.env === 'development' ? (injectedConfig.placeholderEmail || '') : '',
10
+ name: injectedConfig.env === 'development' ? 'Bruce Wayne' : '',
11
+ business: { name: injectedConfig.env === 'development' ? 'Wayne Enterprises' : '' },
12
+ password: injectedConfig.env === 'development' ? '1234' : '',
13
13
  errors: [] as Errors,
14
14
  })
15
15
 
@@ -1,7 +1,7 @@
1
1
  // Component: https://tailwindui.com/components/application-ui/application-shells/sidebar#component-a69d85b6237ea2ad506c00ef1cd39a38
2
2
  import { Dialog, DialogBackdrop, DialogPanel, TransitionChild } from '@headlessui/react'
3
3
  import avatarImg from 'nitro-web/client/imgs/avatar.jpg'
4
- import { isDemo } from 'nitro-web'
4
+ import { injectedConfig } from 'nitro-web'
5
5
  import {
6
6
  Bars3Icon,
7
7
  HomeIcon,
@@ -85,7 +85,7 @@ function SidebarContents ({ Logo, menu, links, version }: SidebarProps) {
85
85
 
86
86
  const _menu = menu || [
87
87
  { name: 'Dashboard', to: '/', Icon: HomeIcon },
88
- { name: isDemo ? 'Design System' : 'Style Guide', to: '/styleguide', Icon: PaintBrushIcon },
88
+ { name: injectedConfig.isDemo ? 'Design System' : 'Style Guide', to: '/styleguide', Icon: PaintBrushIcon },
89
89
  { name: 'Pricing', to: '/pricing', Icon: UsersIcon },
90
90
  { name: 'Signout', to: '/signout', Icon: ArrowLeftCircleIcon },
91
91
  ]
@@ -1,4 +1,4 @@
1
- import { Drop, Dropdown, Field, Select, Button, Checkbox, GithubLink, isDemo, Modal, Calendar } from 'nitro-web'
1
+ import { Drop, Dropdown, Field, Select, Button, Checkbox, GithubLink, Modal, Calendar, injectedConfig } from 'nitro-web'
2
2
  import { getCountryOptions, getCurrencyOptions, ucFirst } from 'nitro-web/util'
3
3
  import { CheckIcon } from '@heroicons/react/20/solid'
4
4
  import { Config } from 'nitro-web/types'
@@ -55,7 +55,7 @@ export function Styleguide({ config }: { config: Config }) {
55
55
  <div class="mb-10 text-left max-w-[1100px]">
56
56
  <GithubLink filename={__filename} />
57
57
  <div class="mb-7">
58
- <h1 class="h1">{isDemo ? 'Design System' : 'Style Guide'}</h1>
58
+ <h1 class="h1">{injectedConfig.isDemo ? 'Design System' : 'Style Guide'}</h1>
59
59
  <p>
60
60
  Components are styled using&nbsp;
61
61
  <a href="https://v3.tailwindcss.com/docs/configuration" class="underline" target="_blank" rel="noreferrer">TailwindCSS</a>.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-web",
3
- "version": "0.0.26",
3
+ "version": "0.0.27",
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 🚀",
@@ -33,18 +33,18 @@
33
33
  "bcrypt": "^5.0.0",
34
34
  "body-parser": "^1.19.0",
35
35
  "compression": "^1.7.4",
36
- "connect-mongo": "^5.1.0",
37
36
  "date-fns": "^3.6.0",
38
37
  "dateformat": "^3.0.3",
39
38
  "dotenv": "^14.3.2",
40
39
  "express": "^4.17.1",
41
40
  "express-fileupload": "^1.1.6",
42
- "express-session": "^1.17.0",
43
41
  "inline-css": "^4.0.2",
42
+ "jsonwebtoken": "^9.0.2",
44
43
  "nodemailer": "^6.5.0",
45
44
  "nodemailer-mailgun-transport": "^2.0.2",
46
45
  "nunjucks": "^3.2.2",
47
46
  "passport": "^0.4.1",
47
+ "passport-jwt": "^4.0.1",
48
48
  "passport-local": "^1.0.0",
49
49
  "sort-route-addresses-nodeps": "0.0.4"
50
50
  },
@@ -7,7 +7,6 @@ import { CSSInterpolation } from '@emotion/serialize'
7
7
  declare global {
8
8
  /** Webpack injected config variables */
9
9
  const INJECTED_CONFIG: Record<string, string|boolean|object>
10
- const ISDEMO: boolean
11
10
  /** Webpack svg loader */
12
11
  module '*.svg' {
13
12
  const content: React.FC<React.SVGProps<SVGElement>>
package/types.ts CHANGED
@@ -1,21 +1,25 @@
1
- // Expected config to be available
2
- export type Config = {
1
+ type InjectedConfig = {
2
+ awsUrl?: string
3
3
  clientUrl: string
4
4
  countries: { [key: string]: { numberFormats: { currency: string } } } // for input-currency.tsx
5
5
  currencies: { [key: string]: { symbol: string, digits: number } } // for input-currency.tsx
6
6
  env: string
7
+ googleMapsApiKey?: string
8
+ isDemo: boolean // implicitly defined by webpack
9
+ isStatic: boolean // implicitly defined by webpack
10
+ jwtName: string // implicitly defined by webpack
7
11
  name: string
12
+ placeholderEmail?: string
13
+ stripePublishableKey?: string
14
+ titleSeparator?: string
8
15
  version: string
16
+ }
9
17
 
10
- awsUrl?: string
18
+ export type Config = InjectedConfig & {
19
+ // Non-injectable config on the client
11
20
  beforeApp?: () => Promise<object>
12
21
  beforeStoreUpdate?: (prevStore: Store | null, newData: Store) => Store
13
- googleMapsApiKey?: string
14
- isStatic?: boolean
15
22
  middleware?: Record<string, (route: unknown, store: Store) => undefined | { redirect: string }>
16
- placeholderEmail?: string
17
- stripePublishableKey?: string
18
- titleSeparator?: string
19
23
  }
20
24
 
21
25
  export type User = {
@@ -45,6 +49,7 @@ export type Store = {
45
49
  message?: MessageObject | string | null
46
50
  user?: User | null,
47
51
  apiAvailable?: boolean
52
+ jwt?: string
48
53
  }
49
54
 
50
55
  export type Svg = React.FC<React.SVGProps<SVGElement>>