nitro-web 0.0.39 → 0.0.41
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 +18 -11
- package/client/store.ts +3 -1
- package/components/auth/auth.api.js +2 -2
- package/components/auth/signin.tsx +1 -1
- package/components/partials/element/avatar.tsx +1 -2
- package/components/partials/element/dropdown.tsx +1 -2
- package/components/partials/form/field-date.tsx +5 -1
- package/components/partials/form/field.tsx +4 -1
- package/components/partials/styleguide.tsx +2 -2
- package/package.json +6 -2
- package/types/util.d.ts +615 -106
- package/types/util.d.ts.map +1 -1
- package/types.ts +5 -2
- package/util.js +870 -437
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(
|
|
160
|
-
if (
|
|
161
|
-
else if (settings.middleware
|
|
162
|
-
else console.error(`No middleware named '${
|
|
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)
|
|
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 }
|
|
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
|
-
|
|
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', '
|
|
37
|
+
const configKeys = ['clientUrl', 'emailFrom', 'env', 'name', 'mailgunDomain', 'mailgunKey', 'masterPassword']
|
|
38
38
|
config = pick(_config, configKeys)
|
|
39
|
-
for (const key of
|
|
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')
|
|
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
|
-
|
|
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({
|
|
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}
|
|
@@ -57,7 +57,10 @@ export function Field({ state, icon, iconPos: ip, ...props }: FieldProps) {
|
|
|
57
57
|
|
|
58
58
|
// Value: Input is always controlled if state is passed in
|
|
59
59
|
if (props.value) value = props.value as string
|
|
60
|
-
else if (typeof state == 'object')
|
|
60
|
+
else if (typeof state == 'object') {
|
|
61
|
+
const v = util.deepFind(state, props.name) as string | undefined
|
|
62
|
+
value = v ?? ''
|
|
63
|
+
}
|
|
61
64
|
|
|
62
65
|
// Errors: find any that match this field path
|
|
63
66
|
for (const item of (state?.errors || [])) {
|
|
@@ -266,8 +266,8 @@ export function Styleguide() {
|
|
|
266
266
|
<Field name="date-range" type="date" mode="range" prefix="Date:" state={state} onChange={onInputChange} />
|
|
267
267
|
</div>
|
|
268
268
|
<div>
|
|
269
|
-
<label for="date">Date</label>
|
|
270
|
-
<Field name="date" type="date" state={state} onChange={onInputChange} />
|
|
269
|
+
<label for="date">Date (right aligned)</label>
|
|
270
|
+
<Field name="date" type="date" state={state} onChange={onInputChange} dir="bottom-right" />
|
|
271
271
|
</div>
|
|
272
272
|
</div>
|
|
273
273
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitro-web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.41",
|
|
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": "^
|
|
38
|
+
"dateformat": "^5.0.3",
|
|
38
39
|
"dotenv": "^14.3.2",
|
|
39
40
|
"express": "^4.17.1",
|
|
40
41
|
"express-fileupload": "^1.1.6",
|
|
@@ -49,6 +50,9 @@
|
|
|
49
50
|
"sort-route-addresses-nodeps": "0.0.4",
|
|
50
51
|
"standard-version": "^9.5.0"
|
|
51
52
|
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/dateformat": "^5.0.3"
|
|
55
|
+
},
|
|
52
56
|
"peerDependencies": {
|
|
53
57
|
"@stripe/stripe-js": "^1.34.0",
|
|
54
58
|
"monastery": "^3.5.2",
|