nitro-web 0.0.38 → 0.0.40

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
@@ -1,7 +1,6 @@
1
1
  import { createBrowserRouter, createHashRouter, redirect, useParams, RouterProvider } from 'react-router-dom'
2
2
  import { Fragment, ReactNode } from 'react'
3
3
  import ReactDOM from 'react-dom/client'
4
- import { AxiosRequestConfig } from '@hokify/axios'
5
4
  import { axios, camelCase, pick, toArray, setTimeoutPromise } from 'nitro-web/util'
6
5
  import { injectedConfig, preloadedStoreData, exposedStoreData } from './index'
7
6
  import { Config, Store } from 'nitro-web/types'
@@ -27,11 +26,16 @@ type Settings = {
27
26
 
28
27
  type Route = {
29
28
  component: React.FC<{ route?: Route; params?: object; location?: object; config?: Config }>
30
- meta?: { title?: string }
31
29
  middleware: string[]
32
30
  name: string
33
31
  path: string
34
32
  redirect?: string
33
+ meta?: { title?: string; layout?: number }
34
+ }
35
+
36
+ type RouteWithPolicies = Route & {
37
+ // Route policies, e.g. { 'get /signin' : ['isUser'] }
38
+ [key: string]: true | string | (string | true)[]
35
39
  }
36
40
 
37
41
  export async function setupApp(config: Config, storeContainer: StoreContainer, layouts: React.FC<LayoutProps>[]) {
@@ -137,7 +141,7 @@ function getRouter({ settings, config }: { settings: Settings, config: Config })
137
141
  for (const key in file) {
138
142
  const isReactFnComponentOrFnRef = typeof file[key] === 'function' || !!file[key]?.render
139
143
  if (!file.hasOwnProperty(key) || key.match(/route/i) || !isReactFnComponentOrFnRef) continue
140
- const componentRoutes = toArray(file[key].route || file.route || file.Route)
144
+ const componentRoutes = toArray(file[key].route || file.route || file.Route) as RouteWithPolicies[]
141
145
  const componentName = key || camelCase(key.replace(/^.*[\\\/]|\.jsx$/g, '')) as string // eslint-disable-line
142
146
  // console.log(file)
143
147
  // Todo: need to retrieve the original function name for default exports during minification.
@@ -153,13 +157,13 @@ function getRouter({ settings, config }: { settings: Settings, config: Config })
153
157
  const routePaths = Object.keys(pick(route, /^(get\s+)?\/|^\*$/))
154
158
 
155
159
  for (const routePath of routePaths) {
156
- const layoutNum = (route.meta?.layout || 1) - 1
160
+ const layoutNum = (parseInt(String(route.meta?.layout || '1')) || 1) - 1
157
161
 
158
162
  // get the routes middleware
159
- const middleware = toArray(route[routePath]).filter(o => {
160
- if (o === true) return // ignore true
161
- else if (settings.middleware[o]) return true
162
- else console.error(`No middleware named '${o}' defined under config.middleware, skipping..`)
163
+ const middleware = toArray(route[routePath]).filter((policyNameOrTrue) => {
164
+ if (policyNameOrTrue === true) return // ignore true
165
+ else if (policyNameOrTrue in settings.middleware) return true
166
+ else console.error(`No middleware named '${policyNameOrTrue}' defined under config.middleware, skipping..`)
163
167
  })
164
168
 
165
169
  // Push route to layout
@@ -171,7 +175,7 @@ function getRouter({ settings, config }: { settings: Settings, config: Config })
171
175
  layout: layoutNum,
172
176
  title: `${route.meta?.title ? `${route.meta.title}${settings.titleSeparator || ' - '}` : ''}${settings.name}`,
173
177
  },
174
- middleware: middleware,
178
+ middleware: middleware as string[],
175
179
  name: componentName,
176
180
  path: routePath,
177
181
  redirect: route.redirect,
@@ -203,7 +207,10 @@ function getRouter({ settings, config }: { settings: Settings, config: Config })
203
207
  path: route.path,
204
208
  loader: async () => { // request
205
209
  // wait for container/exposedStoreData to be setup
206
- if (!nonce) nonce = true && await setTimeoutPromise(() => {}, 0) // eslint-disable-line
210
+ if (!nonce) {
211
+ nonce = true
212
+ await setTimeoutPromise(() => {}, 0)
213
+ }
207
214
  for (const key of route.middleware) {
208
215
  const error = settings.middleware[key](route, exposedStoreData || {})
209
216
  if (error && error.redirect) {
@@ -278,7 +285,7 @@ async function beforeApp(config: Config) {
278
285
  // delete window.prehot
279
286
  // }
280
287
  if (!config.isStatic) {
281
- storeData = (await axios().get('/api/store', { 'axios-retry': { retries: 3 }, timeout: 4000 } as AxiosRequestConfig)).data
288
+ storeData = (await axios().get('/api/store', { 'axios-retry': { retries: 3 }, timeout: 4000 })).data
282
289
  apiAvailable = true
283
290
  }
284
291
  } catch (err) {
package/client/store.ts CHANGED
@@ -40,6 +40,8 @@ function beforeUpdate<T extends Store>(newStore: T) {
40
40
  }
41
41
 
42
42
  // E.g. Cookie matching handy for rare issues, e.g. signout > signin (to a different user on another tab)
43
- axios().defaults.headers.authid = newStore?.user?._id
43
+ if (newStore?.user?._id) {
44
+ axios().defaults.headers.authid = newStore?.user?._id
45
+ }
44
46
  return newStore
45
47
  }
@@ -34,9 +34,9 @@ export default {
34
34
  function setup(middleware, _config) {
35
35
  // Setup is called automatically when the server starts
36
36
  // Set config values
37
- const configKeys = ['clientUrl', 'emailFrom', 'env', 'mailgunDomain', 'mailgunKey', 'masterPassword', 'name']
37
+ const configKeys = ['clientUrl', 'emailFrom', 'env', 'name', 'mailgunDomain', 'mailgunKey', 'masterPassword']
38
38
  config = pick(_config, configKeys)
39
- for (const key of configKeys) {
39
+ for (const key of ['clientUrl', 'emailFrom', 'env', 'name']) {
40
40
  if (!config[key]) throw new Error(`Missing config value for: config.${key}`)
41
41
  }
42
42
 
@@ -17,7 +17,7 @@ export function Signin() {
17
17
  useEffect(() => {
18
18
  // Autofill the email input from ?email=
19
19
  const query = util.queryObject(location.search, true)
20
- if (query.email) setState({ ...state, email: query.email })
20
+ if (query.email) setState({ ...state, email: query.email as string })
21
21
  }, [location.search])
22
22
 
23
23
  useEffect(() => {
@@ -1,6 +1,5 @@
1
1
  import { Initials } from 'nitro-web'
2
2
  import { s3Image } from 'nitro-web/util'
3
- import noImage from 'nitro-web/client/imgs/no-image.svg'
4
3
  import avatarImg from 'nitro-web/client/imgs/avatar.jpg'
5
4
  import { User } from 'nitro-web/types'
6
5
 
@@ -32,7 +31,7 @@ export function Avatar({ awsUrl, isRound, user, showPlaceholderImage, className
32
31
 
33
32
  return (
34
33
  user.avatar
35
- ? <img class={classes} src={s3Image(awsUrl, user.avatar, 'small') || noImage} />
34
+ ? <img class={classes} src={s3Image(awsUrl, user.avatar, 'small')} />
36
35
  : showPlaceholderImage ? <img class={classes} src={avatarImg} width="30px" />
37
36
  : <Initials className={classes} icon={{ initials: getInitials(user), hex: getHex(user) }} isRound={isRound} isMedium={true} />
38
37
  )
@@ -1,6 +1,5 @@
1
1
  import { css } from 'twin.macro'
2
2
  import { forwardRef, cloneElement } from 'react'
3
- import { toArray } from 'nitro-web/util'
4
3
  import { getSelectStyle } from 'nitro-web'
5
4
  import { CheckCircleIcon } from '@heroicons/react/24/solid'
6
5
 
@@ -99,7 +98,7 @@ export const Dropdown = forwardRef(function Dropdown({
99
98
  css={style}
100
99
  >
101
100
  {
102
- toArray(children).map((el, key) => {
101
+ (Array.isArray(children) ? children : [children]).map((el, key) => {
103
102
  const onKeyDown = onMouseDown
104
103
  if (!el.type) throw new Error('Dropdown component requires a valid child element')
105
104
  return cloneElement(el, { key, onMouseDown, onKeyDown }) // adds onClick
@@ -17,6 +17,7 @@ export type FieldDateProps = React.InputHTMLAttributes<HTMLInputElement> & {
17
17
  value?: null|number|string|(null|number|string)[]
18
18
  numberOfMonths?: number
19
19
  Icon?: React.ReactNode
20
+ dir?: 'bottom-left'|'bottom-right'|'top-left'|'top-right'
20
21
  }
21
22
 
22
23
  type TimePickerProps = {
@@ -24,7 +25,9 @@ type TimePickerProps = {
24
25
  onChange: (mode: Mode, value: number|null) => void
25
26
  }
26
27
 
27
- export function FieldDate({ mode='single', onChange, prefix='', value, numberOfMonths, Icon, showTime, ...props }: FieldDateProps) {
28
+ export function FieldDate({
29
+ mode='single', onChange, prefix='', value, numberOfMonths, Icon, showTime, dir = 'bottom-left', ...props
30
+ }: FieldDateProps) {
28
31
  const localePattern = `d MMM yyyy${showTime && mode == 'single' ? ' hh:mmaa' : ''}`
29
32
  const [prefixWidth, setPrefixWidth] = useState(0)
30
33
  const dropdownRef = useRef<DropdownRef>(null)
@@ -100,6 +103,7 @@ export function FieldDate({ mode='single', onChange, prefix='', value, numberOfM
100
103
  {!!showTime && mode == 'single' && <TimePicker date={dates?.[0]} onChange={onCalendarChange} />}
101
104
  </div>
102
105
  }
106
+ dir={dir}
103
107
  >
104
108
  <div className="grid grid-cols-1">
105
109
  {Icon}
@@ -0,0 +1,163 @@
1
+ import React, { useState, useMemo, useCallback, JSX, useEffect } from 'react'
2
+ import { css, theme } from 'twin.macro'
3
+ import { Dropdown } from 'nitro-web'
4
+ import ClockIcon from '../icons/clock'
5
+
6
+ export interface FieldTimeProps {
7
+ className?: string;
8
+ placeholder?: string;
9
+ id?: string;
10
+ onChange: (values: { id: string; value: number; isFalse: boolean }) => void;
11
+ value: string;
12
+ isFull?: boolean;
13
+ Icon?: JSX.Element;
14
+ required?: boolean;
15
+ onFalseCondition?: (id: string, time: number) => boolean
16
+ }
17
+
18
+ export function FieldTime({
19
+ className, placeholder = '', id = '', onChange, value, isFull = true, Icon = <ClockIcon />, required = false, onFalseCondition, ...rest
20
+ }: FieldTimeProps) {
21
+
22
+ // Parse the incoming time value
23
+ const [time, setTime] = useState(() => Number(value) || 0)
24
+
25
+ const handleTimeChange = useCallback((isHour: boolean, e: React.ChangeEvent<HTMLSelectElement>) => {
26
+ const timeStr = e.target.value
27
+ const number = parseInt(timeStr)
28
+ const hours = Math.floor(time / (60 * 60 * 1000))
29
+ const minutes = Math.floor((time % (60 * 60 * 1000)) / (60 * 1000))
30
+ const newTime = isHour ? (number * 60 * 60 * 1000) + (minutes * 60 * 1000) : (hours * 60 * 60 * 1000) + (number * 60 * 1000)
31
+
32
+ const isFalse = onFalseCondition ? onFalseCondition(id, newTime) : false // if false, not update
33
+
34
+ if (!isFalse) setTime(newTime)
35
+
36
+ if (onChange) onChange({ id: id, value: newTime, isFalse: isFalse })
37
+
38
+ }, [time, id, onChange, onFalseCondition])
39
+
40
+ const hoursTime = useMemo(() => Math.floor(time / (60 * 60 * 1000)), [time])
41
+
42
+ const minutesTime = useMemo(() => Math.floor((time % (60 * 60 * 1000)) / (60 * 1000)), [time])
43
+
44
+ const [displayTime, setDisplayTime] = useState('00:00')
45
+
46
+ const secondOptions = useMemo(() => {
47
+ const [_hours, minutes] = displayTime.split(':').map(Number)
48
+ return [...new Set([minutes, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55])]
49
+ }, [displayTime])
50
+
51
+ useEffect(() => {
52
+ setDisplayTime(`${String(hoursTime).padStart(2, '0')}:${String(minutesTime).padStart(2, '0')}`)
53
+ }, [hoursTime, minutesTime])
54
+
55
+ return (
56
+ <Dropdown
57
+ className={isFull ? 'w-full' : 'w-auto'}
58
+ // @ts-ignore
59
+ css={style}
60
+ menuToggles={false}
61
+ animate={false}
62
+ menuChildren={
63
+ <div className="time-picker-container">
64
+ <div className="time-picker-selectors">
65
+ <select
66
+ className="time-select"
67
+ value={hoursTime}
68
+ onChange={(e) => handleTimeChange(true, e)}
69
+ >
70
+ {[...Array(24).keys()].map(i => (
71
+ <option key={i} value={i}>{String(i).padStart(2, '0')}</option>
72
+ ))}
73
+ </select>
74
+ <span className="time-separator">:</span>
75
+ <select
76
+ className="time-select"
77
+ value={minutesTime}
78
+ onChange={(e) => handleTimeChange(false, e)}
79
+ >
80
+ {secondOptions.map(option => (
81
+ <option key={option} value={option}>{String(option).padStart(2, '0')}</option>
82
+ ))}
83
+ </select>
84
+ </div>
85
+ </div>
86
+ }
87
+ >
88
+ <div className="grid grid-cols-1">
89
+ {Icon}
90
+ <input
91
+ {...rest}
92
+ key={id}
93
+ id={id}
94
+ autoComplete="off"
95
+ className={`hide-time-icon font-medium text-sm placeholder-font-medium placeholder-sm ${className}`}
96
+ value={displayTime}
97
+ onChange={(e) => {
98
+ const newStr = e.target.value
99
+ const isValid = /^[0-9]{2}:[0-9]{2}$/.test(newStr)
100
+ if (!isValid) {
101
+ setDisplayTime(newStr)
102
+ return
103
+ }
104
+ const [hours, minutes] = newStr.split(':').map(Number)
105
+ const newNum = (hours * 60 + minutes) * 60 * 1000
106
+
107
+ setTime(newNum)
108
+ setDisplayTime(newStr)
109
+ }}
110
+ placeholder={placeholder}
111
+ required={required}
112
+ />
113
+ </div>
114
+ </Dropdown>
115
+ )
116
+ }
117
+
118
+ export default FieldTime
119
+
120
+ const style = css`
121
+ .time-picker-container {
122
+ padding: 15px;
123
+ font-size: 14px;
124
+ background-color: white;
125
+ border-radius: 8px;
126
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
127
+ }
128
+
129
+ .time-picker-selectors {
130
+ display: flex;
131
+ justify-content: center;
132
+ align-items: center;
133
+ gap: 5px;
134
+ }
135
+
136
+ .time-separator {
137
+ font-size: 18px;
138
+ font-weight: bold;
139
+ }
140
+
141
+ .time-select {
142
+ width: 60px;
143
+ padding: 8px;
144
+ font-size: 14px;
145
+ border: 1px solid #ccc;
146
+ border-radius: 4px;
147
+ }
148
+
149
+ .time-confirm-btn {
150
+ padding: 8px 16px;
151
+ font-size: 16px;
152
+ background-color: ${theme`colors.primary`};
153
+ color: white;
154
+ border: none;
155
+ border-radius: 4px;
156
+ cursor: pointer;
157
+ margin-top: 10px;
158
+ }
159
+
160
+ .time-confirm-btn:hover {
161
+ background-color: ${theme`colors.primary`};
162
+ }
163
+ `
@@ -11,6 +11,8 @@ import {
11
11
  EyeSlashIcon,
12
12
  } from '@heroicons/react/20/solid'
13
13
  // Maybe use fill-current tw class for lucide icons (https://github.com/lucide-icons/lucide/discussions/458)
14
+ import FieldTime, { FieldTimeProps } from './field-time'
15
+ import ClockIcon from '../icons/clock'
14
16
 
15
17
  type InputProps = React.InputHTMLAttributes<HTMLInputElement>
16
18
  type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
@@ -20,7 +22,7 @@ type FieldExtraProps = {
20
22
  id?: string
21
23
  // state object to get the value, and check errors against
22
24
  state?: { errors?: Errors, [key: string]: unknown }
23
- type?: 'text' | 'password' | 'email' | 'filter' | 'search' | 'textarea' | 'currency' | 'date' | 'color'
25
+ type?: 'text' | 'password' | 'email' | 'filter' | 'search' | 'textarea' | 'currency' | 'date' | 'time' | 'color'
24
26
  icon?: React.ReactNode
25
27
  iconPos?: 'left' | 'right'
26
28
  }
@@ -36,6 +38,7 @@ export type FieldProps = (
36
38
  | ({ type: 'currency' } & FieldCurrencyProps & FieldExtraProps)
37
39
  | ({ type: 'color' } & FieldColorProps & FieldExtraProps)
38
40
  | ({ type: 'date' } & FieldDateProps & FieldExtraProps)
41
+ | ({ type: 'time' } & FieldTimeProps & FieldExtraProps)
39
42
  )
40
43
 
41
44
  export function Field({ state, icon, iconPos: ip, ...props }: FieldProps) {
@@ -57,7 +60,10 @@ export function Field({ state, icon, iconPos: ip, ...props }: FieldProps) {
57
60
 
58
61
  // Value: Input is always controlled if state is passed in
59
62
  if (props.value) value = props.value as string
60
- else if (typeof state == 'object') value = util.deepFind(state, props.name) ?? ''
63
+ else if (typeof state == 'object') {
64
+ const v = util.deepFind(state, props.name) as string | undefined
65
+ value = v ?? ''
66
+ }
61
67
 
62
68
  // Errors: find any that match this field path
63
69
  for (const item of (state?.errors || [])) {
@@ -83,6 +89,8 @@ export function Field({ state, icon, iconPos: ip, ...props }: FieldProps) {
83
89
  Icon = <IconWrapper iconPos={iconPos} icon={icon || <ColorSvg hex={value}/>} className="size-[17px]" />
84
90
  } else if (type == 'date') {
85
91
  Icon = <IconWrapper iconPos={iconPos} icon={icon || <CalendarIcon />} className="size-4" />
92
+ } else if (type == 'time') {
93
+ Icon = <IconWrapper iconPos={iconPos} icon={icon || <ClockIcon />} className="size-4" />
86
94
  } else {
87
95
  Icon = <IconWrapper iconPos={iconPos} icon={icon} />
88
96
  }
@@ -122,6 +130,12 @@ export function Field({ state, icon, iconPos: ip, ...props }: FieldProps) {
122
130
  <FieldDate {...props} {...commonProps} Icon={Icon} />
123
131
  </FieldContainer>
124
132
  )
133
+ } else if (type == 'time') {
134
+ return (
135
+ <FieldContainer error={error} className={props.className}>
136
+ <FieldTime {...props} {...commonProps} Icon={Icon} />
137
+ </FieldContainer>
138
+ )
125
139
  }
126
140
  }
127
141
 
@@ -0,0 +1,18 @@
1
+ import { theme } from 'twin.macro'
2
+
3
+ const ClockIcon = () => {
4
+ return (
5
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
6
+ <g opacity="0.5" clipPath="url(#clip0_12681_10783)">
7
+ <path d="M8.00016 3.99998V7.99998L10.6668 9.33331M14.6668 7.99998C14.6668 11.6819 11.6821 14.6666 8.00016 14.6666C4.31826 14.6666 1.3335 11.6819 1.3335 7.99998C1.3335 4.31808 4.31826 1.33331 8.00016 1.33331C11.6821 1.33331 14.6668 4.31808 14.6668 7.99998Z" stroke={theme`colors.input`} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
8
+ </g>
9
+ <defs>
10
+ <clipPath id="clip0_12681_10783">
11
+ <rect width="16" height="16" fill="white" />
12
+ </clipPath>
13
+ </defs>
14
+ </svg>
15
+ )
16
+ }
17
+
18
+ export default ClockIcon
@@ -14,6 +14,7 @@ export function Styleguide() {
14
14
  date: Date.now(),
15
15
  'date-range': [Date.now(), Date.now() + 1000 * 60 * 60 * 24 * 33],
16
16
  'date-time': Date.now(),
17
+ time: '0',
17
18
  calendar: [Date.now(), Date.now() + 1000 * 60 * 60 * 24 * 8],
18
19
  firstName: 'Bruce',
19
20
  errors: [
@@ -39,13 +40,30 @@ export function Styleguide() {
39
40
  ]
40
41
 
41
42
  function onInputChange (e: { target: { id: string, value: unknown } }) {
42
- if ((e.target.id == 'customer' || e.target.id == 'customer2') && e.target.value == '') {
43
+ const {id} = e.target
44
+ let {value} = e.target
45
+ if ((id == 'customer' || id == 'customer2') && value == '') {
43
46
  setCustomerSearch('')
44
- e.target.value = null // clear the selected value
47
+ value = null // clear the selected value
45
48
  }
46
- setState(s => ({ ...s, [e.target.id]: e.target.value }))
49
+ const newValue = value
50
+ console.dir('newValue')
51
+ console.dir(newValue)
52
+ setState(s => ({ ...s, [id]: newValue }))
47
53
  }
48
54
 
55
+ /**
56
+ * handleTimeChange
57
+ */
58
+ const handleTimeChange = () => ({ id, value }: { id: string, value: number | number[] }) => {
59
+ if (Array.isArray(value)) return
60
+
61
+ setState((state) => ({
62
+ ...state,
63
+ [id]: value,
64
+ }))
65
+ }
66
+
49
67
  function onCustomerSearch (search: string) {
50
68
  setCustomerSearch(search || '')
51
69
  }
@@ -266,8 +284,12 @@ export function Styleguide() {
266
284
  <Field name="date-range" type="date" mode="range" prefix="Date:" state={state} onChange={onInputChange} />
267
285
  </div>
268
286
  <div>
269
- <label for="date">Date</label>
270
- <Field name="date" type="date" state={state} onChange={onInputChange} />
287
+ <label for="date">Date (right aligned)</label>
288
+ <Field name="date" type="date" state={state} onChange={onInputChange} dir="bottom-right" />
289
+ </div>
290
+ <div>
291
+ <label for="time">Time</label>
292
+ <Field name="time" type="time" state={state} value={state.time} onChange={handleTimeChange} />
271
293
  </div>
272
294
  </div>
273
295
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-web",
3
- "version": "0.0.38",
3
+ "version": "0.0.40",
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 🚀",
@@ -29,12 +29,13 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@hokify/axios": "^0.19.1",
32
+ "axios": "^1.9.0",
32
33
  "axios-retry": "^3.3.1",
33
34
  "bcrypt": "^5.0.0",
34
35
  "body-parser": "^1.19.0",
35
36
  "compression": "^1.7.4",
36
37
  "date-fns": "^3.6.0",
37
- "dateformat": "^3.0.3",
38
+ "dateformat": "^5.0.3",
38
39
  "dotenv": "^14.3.2",
39
40
  "express": "^4.17.1",
40
41
  "express-fileupload": "^1.1.6",
@@ -46,7 +47,11 @@
46
47
  "passport": "^0.4.1",
47
48
  "passport-jwt": "^4.0.1",
48
49
  "passport-local": "^1.0.0",
49
- "sort-route-addresses-nodeps": "0.0.4"
50
+ "sort-route-addresses-nodeps": "0.0.4",
51
+ "standard-version": "^9.5.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/dateformat": "^5.0.3"
50
55
  },
51
56
  "peerDependencies": {
52
57
  "@stripe/stripe-js": "^1.34.0",
@@ -66,10 +71,9 @@
66
71
  },
67
72
  "bumpFiles": [
68
73
  "package.json",
69
- "package-lock.json",
70
74
  "../webpack/package.json",
71
75
  {
72
- "filename": "../readme.md",
76
+ "filename": "../example/package.json",
73
77
  "updater": "./semver-updater.cjs"
74
78
  }
75
79
  ]