nitro-web 0.0.84 → 0.0.86
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 +2 -0
- package/client/index.ts +0 -4
- package/components/auth/auth.api.js +9 -10
- package/components/auth/reset.tsx +3 -3
- package/components/auth/signin.tsx +3 -3
- package/components/auth/signup.tsx +8 -6
- package/components/partials/form/checkbox.tsx +4 -2
- package/components/partials/form/drop.tsx +4 -2
- package/components/partials/form/field.tsx +18 -9
- package/components/partials/form/select.tsx +6 -2
- package/package.json +1 -1
- package/server/index.js +0 -2
- package/server/models/user.js +8 -2
- package/tsconfig.json +4 -4
- package/types/util.d.ts +5 -3
- package/types/util.d.ts.map +1 -1
- package/util.js +7 -4
- package/components/settings/settings-account.tsx +0 -139
- package/components/settings/settings-business.tsx +0 -117
- package/components/settings/settings-team--member.tsx +0 -106
- package/components/settings/settings-team.tsx +0 -72
- package/components/settings/settings.api.js +0 -51
package/client/app.tsx
CHANGED
|
@@ -94,10 +94,12 @@ function App({ settings, config, storeContainer }: { settings: Settings, config:
|
|
|
94
94
|
}, [!!router])
|
|
95
95
|
|
|
96
96
|
return (
|
|
97
|
+
// <StrictMode>
|
|
97
98
|
<storeContainer.Provider>
|
|
98
99
|
{ router && <RouterProvider router={router}/> }
|
|
99
100
|
<AfterApp settings={settings} />
|
|
100
101
|
</storeContainer.Provider>
|
|
102
|
+
// </StrictMode>
|
|
101
103
|
)
|
|
102
104
|
}
|
|
103
105
|
|
package/client/index.ts
CHANGED
|
@@ -18,10 +18,6 @@ export { ResetInstructions, ResetPassword } from '../components/auth/reset'
|
|
|
18
18
|
export { Dashboard } from '../components/dashboard/dashboard'
|
|
19
19
|
export { NotFound } from '../components/partials/not-found'
|
|
20
20
|
export { Styleguide } from '../components/partials/styleguide'
|
|
21
|
-
// export { SettingsAccount } from '../components/settings/settings-account'
|
|
22
|
-
// export { SettingsBusiness } from '../components/settings/settings-business'
|
|
23
|
-
// export { SettingsTeamMember } from '../components/settings/settings-team--member'
|
|
24
|
-
// export { SettingsTeam } from '../components/settings/settings-team'
|
|
25
21
|
|
|
26
22
|
// Component Elements
|
|
27
23
|
export { Accordion } from '../components/partials/element/accordion'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
import crypto from 'crypto'
|
|
2
3
|
import bcrypt from 'bcrypt'
|
|
3
4
|
import passport from 'passport'
|
|
@@ -315,7 +316,7 @@ export async function signinAndGetStore(user, isDesktop, getStore) {
|
|
|
315
316
|
return { ...store, jwt }
|
|
316
317
|
}
|
|
317
318
|
|
|
318
|
-
export async function userCreate({
|
|
319
|
+
export async function userCreate({ business, password, ...userDataProp }) {
|
|
319
320
|
try {
|
|
320
321
|
if (!this.findUserFromProvider) {
|
|
321
322
|
throw new Error('this.findUserFromProvider doesn\'t exist, make sure the context is available when calling this function')
|
|
@@ -330,14 +331,15 @@ export async function userCreate({ name, business, email, password }) {
|
|
|
330
331
|
users: [{ _id: userId, role: 'owner', status: 'active' }],
|
|
331
332
|
}
|
|
332
333
|
const userData = {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
334
|
+
...userDataProp,
|
|
335
|
+
_id: userId,
|
|
336
|
+
...(userDataProp.name ? {
|
|
337
|
+
firstName: fullNameSplit(userDataProp.name)[0],
|
|
338
|
+
lastName: fullNameSplit(userDataProp.name)[1],
|
|
339
|
+
} : {}),
|
|
337
340
|
password: password ? await bcrypt.hash(password, 10) : undefined,
|
|
338
341
|
...(isMultiTenant ? { company: companyData._id } : {}),
|
|
339
342
|
}
|
|
340
|
-
|
|
341
343
|
// First validate the data so we don't have to create a transaction
|
|
342
344
|
const results = await Promise.allSettled([
|
|
343
345
|
db.user.validate(userData, options),
|
|
@@ -362,10 +364,7 @@ export async function userCreate({ name, business, email, password }) {
|
|
|
362
364
|
|
|
363
365
|
} catch (err) {
|
|
364
366
|
if (!isArray(err)) throw err
|
|
365
|
-
throw err
|
|
366
|
-
if (o.title == 'firstName') o.title = 'name'
|
|
367
|
-
return o
|
|
368
|
-
})
|
|
367
|
+
else throw err //...
|
|
369
368
|
}
|
|
370
369
|
}
|
|
371
370
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Topbar, Field, FormError, Button,
|
|
1
|
+
import { Topbar, Field, FormError, Button, request } from 'nitro-web'
|
|
2
2
|
import { Errors } from 'nitro-web/types'
|
|
3
3
|
|
|
4
4
|
export function ResetInstructions() {
|
|
@@ -9,7 +9,7 @@ export function ResetInstructions() {
|
|
|
9
9
|
|
|
10
10
|
async function onSubmit (event: React.FormEvent<HTMLFormElement>) {
|
|
11
11
|
try {
|
|
12
|
-
await
|
|
12
|
+
await request('post /api/reset-instructions', state, event, isLoading, setState)
|
|
13
13
|
setStore((s) => ({ ...s, message: 'Done! Please check your email.' }))
|
|
14
14
|
navigate('/signin')
|
|
15
15
|
} catch (e) {
|
|
@@ -52,7 +52,7 @@ export function ResetPassword() {
|
|
|
52
52
|
|
|
53
53
|
async function onSubmit (event: React.FormEvent<HTMLFormElement>) {
|
|
54
54
|
try {
|
|
55
|
-
const data = await
|
|
55
|
+
const data = await request('post /api/reset-password', state, event, isLoading, setState)
|
|
56
56
|
setStore((s) => ({ ...s, ...data }))
|
|
57
57
|
navigate('/')
|
|
58
58
|
} catch (e) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Topbar, Field, Button, FormError,
|
|
1
|
+
import { Topbar, Field, Button, FormError, request, queryObject, injectedConfig, updateJwt } from 'nitro-web'
|
|
2
2
|
import { Errors } from 'nitro-web/types'
|
|
3
3
|
|
|
4
4
|
export function Signin() {
|
|
@@ -15,7 +15,7 @@ export function Signin() {
|
|
|
15
15
|
|
|
16
16
|
useEffect(() => {
|
|
17
17
|
// Autofill the email input from ?email=
|
|
18
|
-
const query =
|
|
18
|
+
const query = queryObject(location.search, true)
|
|
19
19
|
if (query.email) setState({ ...state, email: query.email as string })
|
|
20
20
|
}, [location.search])
|
|
21
21
|
|
|
@@ -33,7 +33,7 @@ export function Signin() {
|
|
|
33
33
|
|
|
34
34
|
async function onSubmit (e: React.FormEvent<HTMLFormElement>) {
|
|
35
35
|
try {
|
|
36
|
-
const data = await
|
|
36
|
+
const data = await request('post /api/signin', state, e, isLoading, setState)
|
|
37
37
|
// Keep it loading until we navigate
|
|
38
38
|
isLoading[1](true)
|
|
39
39
|
setStore((s) => ({ ...s, ...data }))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Button, Field, FormError, Topbar,
|
|
1
|
+
import { Button, Field, FormError, Topbar, request, injectedConfig } from 'nitro-web'
|
|
2
2
|
import { Errors } from 'nitro-web/types'
|
|
3
3
|
|
|
4
4
|
export function Signup() {
|
|
@@ -15,12 +15,11 @@ export function Signup() {
|
|
|
15
15
|
|
|
16
16
|
async function onSubmit (e: React.FormEvent<HTMLFormElement>) {
|
|
17
17
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
setStore((s) => ({ ...s, ...data }))
|
|
18
|
+
const data = await request('post /api/signup', state, e, isLoading, setState)
|
|
19
|
+
setStore((prev) => ({ ...prev, ...data }))
|
|
21
20
|
setTimeout(() => navigate('/'), 0) // wait for setStore
|
|
22
21
|
} catch (e) {
|
|
23
|
-
|
|
22
|
+
setState((prev) => ({ ...prev, errors: e as Errors }))
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
25
|
|
|
@@ -32,7 +31,10 @@ export function Signup() {
|
|
|
32
31
|
<div class="grid grid-cols-2 gap-6">
|
|
33
32
|
<div>
|
|
34
33
|
<label for="name">Your Name</label>
|
|
35
|
-
<Field name="name" placeholder="E.g. Bruce Wayne" state={state}
|
|
34
|
+
<Field name="name" placeholder="E.g. Bruce Wayne" state={state}
|
|
35
|
+
onChange={(e) => onChange(setState, e)}
|
|
36
|
+
errorTitle={/^(name|firstName|lastName)$/} // if different from `name`
|
|
37
|
+
/>
|
|
36
38
|
</div>
|
|
37
39
|
<div>
|
|
38
40
|
<label for="business.name">Company Name</label>
|
|
@@ -16,14 +16,16 @@ type CheckboxProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
|
|
16
16
|
checkboxClassName?: string
|
|
17
17
|
svgClassName?: string
|
|
18
18
|
labelClassName?: string
|
|
19
|
+
/** title used to find related error messages */
|
|
20
|
+
errorTitle?: string|RegExp
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export function Checkbox({
|
|
22
|
-
state, size, subtext, text, type='checkbox', className, checkboxClassName, svgClassName, labelClassName, ...props
|
|
24
|
+
state, size, subtext, text, type='checkbox', className, checkboxClassName, svgClassName, labelClassName, errorTitle, ...props
|
|
23
25
|
}: CheckboxProps) {
|
|
24
26
|
// Checkbox/radio/toggle component
|
|
25
27
|
let value!: boolean
|
|
26
|
-
const error = getErrorFromState(state, props.name)
|
|
28
|
+
const error = getErrorFromState(state, errorTitle || props.name)
|
|
27
29
|
const id = props.id || props.name
|
|
28
30
|
|
|
29
31
|
if (!props.name) throw new Error('Checkbox requires a `name` prop')
|
|
@@ -21,16 +21,18 @@ type DropProps = {
|
|
|
21
21
|
errors?: Errors
|
|
22
22
|
[key: string]: unknown
|
|
23
23
|
}
|
|
24
|
+
/** title used to find related error messages */
|
|
25
|
+
errorTitle?: string|RegExp
|
|
24
26
|
/** Props to pass to the input element */
|
|
25
27
|
[key: string]: unknown
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
type Image = File | FileList | MonasteryImage | null
|
|
29
31
|
|
|
30
|
-
export function Drop({ awsUrl, className, id, name, onChange, multiple, state, ...props }: DropProps) {
|
|
32
|
+
export function Drop({ awsUrl, className, id, name, onChange, multiple, state, errorTitle, ...props }: DropProps) {
|
|
31
33
|
if (!name) throw new Error('Drop component requires a `name` prop')
|
|
32
34
|
let value: Image = null
|
|
33
|
-
const error = getErrorFromState(state, name)
|
|
35
|
+
const error = getErrorFromState(state, errorTitle || name)
|
|
34
36
|
const inputId = id || name
|
|
35
37
|
const [urls, setUrls] = useState([])
|
|
36
38
|
const stateRef = useRef(state)
|
|
@@ -25,6 +25,8 @@ type FieldExtraProps = {
|
|
|
25
25
|
/** Dependencies to break the implicit memoization of onChange/onInputChange */
|
|
26
26
|
deps?: unknown[]
|
|
27
27
|
placeholder?: string
|
|
28
|
+
/** title used to find related error messages */
|
|
29
|
+
errorTitle?: string|RegExp
|
|
28
30
|
}
|
|
29
31
|
type IconWrapperProps = {
|
|
30
32
|
iconPos: 'left' | 'right'
|
|
@@ -43,17 +45,18 @@ type IsFieldCachedProps = {
|
|
|
43
45
|
name: string
|
|
44
46
|
state?: FieldProps['state']
|
|
45
47
|
deps?: FieldProps['deps']
|
|
48
|
+
errorTitle?: FieldProps['errorTitle']
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
export const Field = memo(FieldBase, (prev, next) => {
|
|
49
52
|
return isFieldCached(prev, next)
|
|
50
53
|
})
|
|
51
54
|
|
|
52
|
-
function FieldBase({ state, icon, iconPos: ip, ...props }: FieldProps) {
|
|
55
|
+
function FieldBase({ state, icon, iconPos: ip, errorTitle, ...props }: FieldProps) {
|
|
53
56
|
// `type` must be kept as props.type for TS to be happy and follow the conditions below
|
|
54
57
|
let value!: string
|
|
55
58
|
let Icon!: React.ReactNode
|
|
56
|
-
const error = getErrorFromState(state, props.name)
|
|
59
|
+
const error = getErrorFromState(state, errorTitle || props.name)
|
|
57
60
|
const type = props.type
|
|
58
61
|
const iconPos = ip == 'left' || (type == 'color' && !ip) ? 'left' : 'right'
|
|
59
62
|
const id = props.id || props.name
|
|
@@ -184,13 +187,9 @@ function ColorSvg({ hex }: { hex?: string }) {
|
|
|
184
187
|
export function isFieldCached(prev: IsFieldCachedProps, next: IsFieldCachedProps) {
|
|
185
188
|
// Check if the field is cached, onChange/onInputChange doesn't affect the cache
|
|
186
189
|
const path = prev.name
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// If the state error has changed, re-render!
|
|
191
|
-
if (getErrorFromState(state, path) !== getErrorFromState(next.state || {}, path)) return false
|
|
192
|
-
// If `deps` have changed, handy for onChange/onInputChange, re-render!
|
|
193
|
-
if ((next.deps?.length !== prev.deps?.length) || next.deps?.some((v, i) => v !== prev.deps?.[i])) return false
|
|
190
|
+
const prevState = prev.state || {}
|
|
191
|
+
const nextState = next.state || {}
|
|
192
|
+
const errorTitle = next.errorTitle || path
|
|
194
193
|
|
|
195
194
|
// Check if any prop has changed, except `onChange`/`onInputChange`
|
|
196
195
|
const allKeys = new Set([...Object.keys(prev), ...Object.keys(next)])
|
|
@@ -201,6 +200,16 @@ export function isFieldCached(prev: IsFieldCachedProps, next: IsFieldCachedProps
|
|
|
201
200
|
return false
|
|
202
201
|
}
|
|
203
202
|
}
|
|
203
|
+
|
|
204
|
+
// If `deps` have changed, handy for onChange/onInputChange, re-render!
|
|
205
|
+
if ((next.deps?.length !== prev.deps?.length) || next.deps?.some((v, i) => v !== prev.deps?.[i])) return false
|
|
206
|
+
|
|
207
|
+
// If the state value has changed, re-render!
|
|
208
|
+
if (deepFind(prevState, path) !== deepFind(nextState, path)) return false
|
|
209
|
+
|
|
210
|
+
// If the state error has changed, re-render!
|
|
211
|
+
if (getErrorFromState(prevState, errorTitle) !== getErrorFromState(nextState, errorTitle)) return false
|
|
212
|
+
|
|
204
213
|
// All good, use cached version
|
|
205
214
|
return true
|
|
206
215
|
}
|
|
@@ -43,6 +43,8 @@ export type SelectProps = {
|
|
|
43
43
|
mode?: 'country'|'customer'|''
|
|
44
44
|
/** Pass dependencies to break memoization, handy for onChange/onInputChange **/
|
|
45
45
|
deps?: unknown[]
|
|
46
|
+
/** title used to find related error messages */
|
|
47
|
+
errorTitle?: string|RegExp
|
|
46
48
|
/** All other props are passed to react-select **/
|
|
47
49
|
[key: string]: unknown
|
|
48
50
|
}
|
|
@@ -51,9 +53,11 @@ export const Select = memo(SelectBase, (prev, next) => {
|
|
|
51
53
|
return isFieldCached(prev, next)
|
|
52
54
|
})
|
|
53
55
|
|
|
54
|
-
function SelectBase({
|
|
56
|
+
function SelectBase({
|
|
57
|
+
id, containerId, minMenuWidth, name, prefix='', onChange, options, state, mode='', errorTitle, ...props
|
|
58
|
+
}: SelectProps) {
|
|
55
59
|
let value: unknown|unknown[]
|
|
56
|
-
const error = getErrorFromState(state, name)
|
|
60
|
+
const error = getErrorFromState(state, errorTitle || name)
|
|
57
61
|
if (!name) throw new Error('Select component requires a `name` and `options` prop')
|
|
58
62
|
|
|
59
63
|
// Get value from value or state
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitro-web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.86",
|
|
4
4
|
"repository": "github:boycce/nitro-web",
|
|
5
5
|
"homepage": "https://boycce.github.io/nitro-web/",
|
|
6
6
|
"description": "Nitro is a battle-tested, modular base project to turbocharge your projects, styled using Tailwind 🚀",
|
package/server/index.js
CHANGED
|
@@ -20,10 +20,8 @@ export { sendEmail } from './email/index.js'
|
|
|
20
20
|
|
|
21
21
|
// Export API controllers
|
|
22
22
|
export * from '../components/auth/auth.api.js'
|
|
23
|
-
export * from '../components/settings/settings.api.js'
|
|
24
23
|
export * from '../components/billing/stripe.api.js'
|
|
25
24
|
|
|
26
25
|
export { routes as authRoutes } from '../components/auth/auth.api.js'
|
|
27
|
-
export { routes as settingsRoutes } from '../components/settings/settings.api.js'
|
|
28
26
|
export { routes as stripeRoutes } from '../components/billing/stripe.api.js'
|
|
29
27
|
|
package/server/models/user.js
CHANGED
|
@@ -8,7 +8,7 @@ export default {
|
|
|
8
8
|
company: { model: 'company', required: true },
|
|
9
9
|
email: { type: 'email', required: true, index: 'unique' },
|
|
10
10
|
firstName: { type: 'string', required: true },
|
|
11
|
-
lastName: { type: 'string' },
|
|
11
|
+
lastName: { type: 'string', required: true },
|
|
12
12
|
password: { type: 'string', minLength: 6 },
|
|
13
13
|
resetToken: { type: 'string' },
|
|
14
14
|
status: { type: 'string', default: 'active', enum: ['active', 'deleted', 'inactive'] },
|
|
@@ -22,6 +22,12 @@ export default {
|
|
|
22
22
|
findBL: ['password', 'resetToken'],
|
|
23
23
|
updateBL: ['company', 'password', 'resetToken', 'status', 'stripeSubscription', 'type', 'usedFreeTrial'],
|
|
24
24
|
|
|
25
|
+
messages: {
|
|
26
|
+
lastName: {
|
|
27
|
+
required: 'A full name is required',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
|
|
25
31
|
beforeValidate: [
|
|
26
32
|
async function (data) {
|
|
27
33
|
if (data.email) data.email = data.email.trim().toLowerCase()
|
|
@@ -32,7 +38,7 @@ export default {
|
|
|
32
38
|
|
|
33
39
|
afterFind: [
|
|
34
40
|
async function (data) {
|
|
35
|
-
if (!data) return
|
|
41
|
+
if (!data) return data
|
|
36
42
|
data.name = fullName(data)
|
|
37
43
|
},
|
|
38
44
|
],
|
package/tsconfig.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"jsx": "react-jsx",
|
|
11
11
|
"lib": ["es6", "dom", "dom.iterable", "esnext"],
|
|
12
12
|
"module": "esnext",
|
|
13
|
-
"moduleResolution": "
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
14
|
"noEmit": false,
|
|
15
15
|
"noFallthroughCasesInSwitch": true,
|
|
16
16
|
"noImplicitAny": true,
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"types": ["./types.ts"]
|
|
23
23
|
},
|
|
24
24
|
"resolveJsonModule": true,
|
|
25
|
+
"rootDir": "./",
|
|
25
26
|
"skipLibCheck": true,
|
|
26
27
|
"sourceMap": true,
|
|
27
28
|
"strict": true,
|
|
@@ -29,9 +30,8 @@
|
|
|
29
30
|
},
|
|
30
31
|
"include": [
|
|
31
32
|
"client",
|
|
32
|
-
"components/**/*.tsx",
|
|
33
|
-
"
|
|
34
|
-
"server/constants.js",
|
|
33
|
+
"components/**/*.{tsx,ts,js}",
|
|
34
|
+
"server/**/*.js",
|
|
35
35
|
"types",
|
|
36
36
|
"./types/core-only-globals.d.ts"
|
|
37
37
|
]
|
package/types/util.d.ts
CHANGED
|
@@ -266,7 +266,7 @@ export function getCurrencyOptions(currencies: {
|
|
|
266
266
|
/**
|
|
267
267
|
* Returns an error from a state object matching the path
|
|
268
268
|
* @param {{ errors?: { title: string, detail: string }[] }|undefined} state
|
|
269
|
-
* @param {string} path
|
|
269
|
+
* @param {string|RegExp} path
|
|
270
270
|
* @returns {{ title: string, detail: string }|undefined}
|
|
271
271
|
*/
|
|
272
272
|
export function getErrorFromState(state: {
|
|
@@ -274,7 +274,7 @@ export function getErrorFromState(state: {
|
|
|
274
274
|
title: string;
|
|
275
275
|
detail: string;
|
|
276
276
|
}[];
|
|
277
|
-
} | undefined, path: string): {
|
|
277
|
+
} | undefined, path: string | RegExp): {
|
|
278
278
|
title: string;
|
|
279
279
|
detail: string;
|
|
280
280
|
} | undefined;
|
|
@@ -621,6 +621,7 @@ export function queryString(obj?: {
|
|
|
621
621
|
* @param {{ [key: string]: any }} [data] - payload
|
|
622
622
|
* @param {{preventDefault?: function}} [event] - event to prevent default
|
|
623
623
|
* @param {[boolean, (value: boolean) => void]} [isLoading] - [isLoading, setIsLoading]
|
|
624
|
+
* @param {SetState} [setState] - if passed, state.errors will be reset before the request
|
|
624
625
|
* @returns {Promise<any>}
|
|
625
626
|
*
|
|
626
627
|
* @example
|
|
@@ -631,7 +632,7 @@ export function request(route: string, data?: {
|
|
|
631
632
|
[key: string]: any;
|
|
632
633
|
}, event?: {
|
|
633
634
|
preventDefault?: Function;
|
|
634
|
-
}, isLoading?: [boolean, (value: boolean) => void]): Promise<any>;
|
|
635
|
+
}, isLoading?: [boolean, (value: boolean) => void], setState?: SetState): Promise<any>;
|
|
635
636
|
/**
|
|
636
637
|
* Removes undefined from an array or object
|
|
637
638
|
* @param {[]|{[key: string]: any}} variable
|
|
@@ -784,4 +785,5 @@ export type Image = {
|
|
|
784
785
|
base64?: string;
|
|
785
786
|
date?: number;
|
|
786
787
|
};
|
|
788
|
+
export type SetState = import("react").Dispatch<import("react").SetStateAction<any>>;
|
|
787
789
|
//# sourceMappingURL=util.d.ts.map
|
package/types/util.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../util.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../util.js"],"names":[],"mappings":"AAoBA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+BC;AAED;;;;;;;;;GASG;AACH,yBARa,OAAO,OAAO,EAAE,WAAW,CAoBvC;AAED;;;;;GAKG;AACH,8BAJW,MAAM,cACN;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAC,GACrB,MAAM,CAKlB;AAED;;;;;;GAMG;AACH,+BALW,MAAM,oBACN,OAAO,gBACP,OAAO,GACL,MAAM,CAelB;AAED;;;;;GAKG;AACH,sCAJW,MAAM,wBACN,OAAO,GACL,MAAM,CAMlB;AAED;;;;GAIG;AACH,sCAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,iCAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;;GAMG;AACH,gCALW,MAAM,aACN,MAAM,oBACN,MAAM,GACJ,MAAM,CAUlB;AAED;;;;GAIG;AACH,0CAHW,MAAM,GACJ,MAAM,CAMlB;AAED;;;;;;;;;;;GAWG;AACH,4BAVW,MAAM,GAAC,IAAI,WACX,MAAM,aACN,MAAM,GACJ,MAAM,CAsBlB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,yBAlBuC,CAAC,SAA3B,CAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAI,QAI3B,CAAC,SACD,MAAM,YACN;IACN,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,GACS,CAAC,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG;IACpD,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,CAAA;CAC7B,CAuKH;AAED;;;;;GAKG;AACH,yBAJa,CAAC,OACH,CAAC,GACC,CAAC,CAgBb;AAED;;;;;GAKG;AACH,8BAJW,MAAM,GAAC,GAAG,EAAE,QACZ,MAAM,GACJ,OAAO,CAgBnB;AAED;;;;;;;GAOG;AACH,yBANa,CAAC,OACH,CAAC,QACD,MAAM,SACN,OAAO,WAAS,GACd,CAAC,CA8Bb;AAED;;;;;;GAMG;AACH,0BALW;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAC,GAAC,EAAE,GAAC,IAAI,gCAE5B,MAAM,GACJ,MAAM,GAAC,EAAE,GAAC,IAAI,CAmB1B;AAED;;;;;;;;;GASG;AACH,mCARW,MAAM,GAAC,IAAI,GAAC,IAAI,YAChB,MAAM,SACN,MAAM,QACN,MAAM,GACJ,IAAI,CA+BhB;AAED;;;;;GAKG;AACH,mCAJW,MAAM,iBACN,OAAO,GACL,MAAM,CAMlB;AAED;;;;GAIG;AACH,mCAHW,MAAM,GACJ,MAAM,CAWlB;AAED;;;;;;;;GAQG;AACH,8BAPW,MAAM,QACN;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAAE,qBAC5G,QAAQ,cACR,MAAM,GACJ,QAAQ,CAwEpB;AAED;;;;GAIG;AACH,iCAHW;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,GACnC,MAAM,CAIlB;AAED;;;;GAIG;AACH,sCAHW,MAAM,GACJ,MAAM,EAAE,CASpB;AAED;;;;GAIG;AACH,6CAHW;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACjC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAS5D;AAED;;;;GAIG;AACH,+CAHW;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACjC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAS9C;AAED;;;;;GAKG;AACH,yCAJW;IAAE,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,GAAC,SAAS,QAC1D,MAAM,GAAC,MAAM,GACX;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAC,SAAS,CAQvD;AAED;;;;;GAKG;AACH,uCAJW,MAAM,iBACN,MAAM,GACJ,MAAM,CAYlB;AAED;;;;;GAKG;AACH,qCAJW;IAAE,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,CAAA;CAAE,QACvC,MAAM,GACJ,MAAM,CAYlB;AAED;;;;GAIG;AACH,6DAHW,MAAM,GACJ,OAAO,CAAC,OAAO,mBAAmB,EAAE,MAAM,GAAC,IAAI,CAAC,CAI5D;AAED;;;;;;;;;;;GAWG;AACH,wCAHW,aAAa,GACX,UAAU,EAAE,CAgCxB;AAED;;;;;;GAMG;AACH,+BALW,GAAG,EAAE,UACL,OAAO,QACP,MAAM,GACJ,OAAO,CAcnB;AAED;;;;GAIG;AACH,kCAHW,OAAO,GACL,OAAO,CAInB;AAED;;;;GAIG;AACH,iCAHW,OAAO,GACL,OAAO,CAInB;AAED;;;;GAIG;AACH,oCAHW,OAAO,GACL,OAAO,CAInB;AAED;;;;GAIG;AACH,+BAHW,MAAM,GACJ,OAAO,CAMnB;AAED;;;;;GAKG;AACH,8BAJW;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAC,GAAC,IAAI,qBAC7B,OAAO,GACL,OAAO,CASnB;AAED;;;;GAIG;AACH,qCAHW,OAAO,GACL,OAAO,CAInB;AAED;;;;GAIG;AACH,+BAHW,OAAO,GACL,OAAO,CAmBnB;AAED;;;;GAIG;AACH,mCAHW,OAAO,GACL,OAAO,CAInB;AAED;;;;GAIG;AACH,mCAHW,OAAO,GACL,OAAO,CAKnB;AAED;;;;GAIG;AACH,kCAHW,OAAO,GACL,OAAO,CAInB;AAED;;;;GAIG;AACH,mCAHW,OAAO,GACL,OAAO,CAInB;AAED;;;;GAIG;AACH,gCAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;;GAMG;AACH,kCALW,MAAM,QACN,MAAM,iBACN,OAAO,GACL,MAAM,CAalB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qCAxBW,MAAM,mBACN,KAAK,GAAC,GAAG,aACT,KAAK,GACH,CAAC,KAAK,EAAE,KAAK,CAAC,GAAC,IAAI,CAuC/B;AAED;;;;;;;;;GASG;AACH,qDARW;IACN,IAAI,CAAC,EAAE;QAAC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAC,CAAA;IACjE,QAAQ,CAAC,EAAE;QAAC,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAC,CAAA;CAC3C,MACO,MAAM,UACN,MAAM,OA+ChB;AAED;;;;;GAKG;AACH,6CAJW,MAAM,EAAE,UACR,MAAM,EAAE,GACP,MAAM,CAgBjB;AAED;;;;GAIG;AACH,kCAHW;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,MACtB,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,KAAK,GAAG;;EAS1C;AAED;;;;;GAKG;AACH,0BAJW;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,UAC1B,MAAM,EAAE,GACN;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAStC;AAED;;;;;;;;;;;;;GAaG;AACH,yBAVa,CAAC,YACH,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,oBACvC;IAAC,MAAM,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAC,CAAA;CAAC,GAAC,CAAC,MAAM,EAAE,WAAS,OAAO,CAAC,8BAEjE,OAAO,CAAC,CAAC,CAAC,CA2DtB;AAED;;;;;;GAMG;AACH,0BALW,MAAM,YACN,MAAM,eACN,MAAM,GACJ,MAAM,CAUlB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,oCAtBW;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,UAOzB;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAC,QAAQ,GAAC,QAAQ,GAAC,WAAW,GAAC,MAAM,EAAE,CAAA;CAAE;;cAgBzB,MAAM;eAAS,MAAM;;iBAAe,MAAM;;EAyC7F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wCAfW;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,GAAC,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,SAMnD;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,UACzC,MAAM;;;;;;EA4BhB;AAED;;;;GAIG;AACH,0BAHW;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,QACtB,MAAM,GAAC,MAAM,GAAC,MAAM,EAAE,GAAC,MAAM,EAAE;;EAiBzC;AAED;;;;;;;GAOG;AACH,0CALW,MAAM,iBACN,OAAO,GACL;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAC,IAAI,CAAA;CAAC,CAsBxC;AAED;;;;GAIG;AACH,yCAHW,MAAM,GACJ,MAAM,EAAE,CAOpB;AAED;;;;;;GAMG;AACH,kCALW;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAC,UACxB,MAAM,YACN;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAC,GACrB,MAAM,CAgBlB;AAED;;;;;;;;;;;;GAYG;AACH,+BAXW,MAAM,SACN;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,UACtB;IAAC,cAAc,CAAC,WAAU;CAAC,cAC3B,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,aACnC,QAAQ,GACN,OAAO,CAAC,GAAG,CAAC,CAyDxB;AAED;;;;GAIG;AACH,0CAHW,EAAE,GAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAC,GACrB,EAAE,GAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAC,CAcnC;AAED;;;;;;;;GAQG;AACH,gCANW,MAAM,gBACN,KAAK,EAAE,GAAC,KAAK,SACb,MAAM,MACN,MAAM,GACJ,MAAM,CAclB;AAED;;;;GAIG;AACH,qCAHW,MAAM,GACJ,MAAM,CAMlB;AAED;;;;;;;;GAQG;AACH,yCAPW,MAAM,gBACN,MAAM,wBAEN,MAAM,aADN,MAAM,GAEJ,MAAM,CA8ClB;AAED;;;;;GAKG;AACH,uCAJW,MAAM,cACN,OAAO,GACL,MAAM,CAelB;AAED;;;;;GAKG;AACH,gEAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAMzB;AAED;;;;GAIG;AACH,oDAFW,aAAa,QAKvB;AAED;;;;;GAKG;AACH,sCAJW;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAC,EAAE,OACtB,MAAM,GACJ,MAAM,EAAE,CAQpB;AAED;;;;;;;;;;;GAWG;AACH,+BAVW,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,SACvB,MAAM,YACN;IACL,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACrB,YAoBH;AAED;;;;;GAKG;AACH,wBAJa,CAAC,YACH,CAAC,GAAG,SAAS,GACX,CAAC,CAAC,SAAS,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CASvC;AAED;;;;GAIG;AACH,6BAHW,MAAM,GACJ,MAAM,CAKlB;AAwED;;;;GAIG;AACH,gCAHW,MAAM,GACJ,MAAM,CAKlB;AA3ED,2FAiEE;;;;yBAz+BW;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;;;;yBACjC;IAAE,MAAM,EAAE,MAAM;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE;;;;8BACrC;IAAE,QAAQ,EAAE;QAAE,IAAI,EAAE;YAAE,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE;;;;4BAE7F,KAAK,GAAC,UAAU,EAAE,GAAC,UAAU,GAAC,eAAe,GAAC,MAAM,GAAC,GAAG;;;;oBAoNxD,CAAC,MAAM,EAAE,MAAM,CAAC;;;;kBAChB;IAAC,UAAU,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAA;CAAC;;;;oBAugBpC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAC;uBA18C7D,OAAO,OAAO,EAAE,QAAQ,CAAC,OAAO,OAAO,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC"}
|
package/util.js
CHANGED
|
@@ -4,6 +4,8 @@ import dateformat from 'dateformat'
|
|
|
4
4
|
import { loadStripe } from '@stripe/stripe-js/pure.js' // pure removes ping
|
|
5
5
|
import { twMerge as _twMerge } from 'tailwind-merge'
|
|
6
6
|
|
|
7
|
+
/** @typedef {import('react').Dispatch<import('react').SetStateAction<any>>} SetState */
|
|
8
|
+
|
|
7
9
|
/** @type {{[key: string]: {[key: string]: string|true}}} */
|
|
8
10
|
let queryObjectCache = {}
|
|
9
11
|
|
|
@@ -689,7 +691,7 @@ export function getCurrencyOptions (currencies) {
|
|
|
689
691
|
/**
|
|
690
692
|
* Returns an error from a state object matching the path
|
|
691
693
|
* @param {{ errors?: { title: string, detail: string }[] }|undefined} state
|
|
692
|
-
* @param {string} path
|
|
694
|
+
* @param {string|RegExp} path
|
|
693
695
|
* @returns {{ title: string, detail: string }|undefined}
|
|
694
696
|
*/
|
|
695
697
|
export function getErrorFromState (state, path) {
|
|
@@ -1403,13 +1405,14 @@ export function queryString (obj, _path='', _output) {
|
|
|
1403
1405
|
* @param {{ [key: string]: any }} [data] - payload
|
|
1404
1406
|
* @param {{preventDefault?: function}} [event] - event to prevent default
|
|
1405
1407
|
* @param {[boolean, (value: boolean) => void]} [isLoading] - [isLoading, setIsLoading]
|
|
1408
|
+
* @param {SetState} [setState] - if passed, state.errors will be reset before the request
|
|
1406
1409
|
* @returns {Promise<any>}
|
|
1407
1410
|
*
|
|
1408
1411
|
* @example
|
|
1409
1412
|
* - request('post /api/user', { name: 'John' })
|
|
1410
1413
|
* - request(`get /api/user/${id}`, undefined, e, isLoading)
|
|
1411
1414
|
*/
|
|
1412
|
-
export async function request (route, data, event, isLoading) {
|
|
1415
|
+
export async function request (route, data, event, isLoading, setState) {
|
|
1413
1416
|
try {
|
|
1414
1417
|
if (event?.preventDefault) event.preventDefault()
|
|
1415
1418
|
const uri = route.replace(/^(post|put|delete|get) /, '')
|
|
@@ -1426,7 +1429,7 @@ export async function request (route, data, event, isLoading) {
|
|
|
1426
1429
|
|
|
1427
1430
|
// warning, not persisting through re-renders, but should be fine until loading is finished
|
|
1428
1431
|
data = data || {}
|
|
1429
|
-
|
|
1432
|
+
if (setState) setState((/** @type {{[key: string]: any}} */prev) => ({ ...prev, errors: [] }))
|
|
1430
1433
|
|
|
1431
1434
|
// Find out if the data has files?
|
|
1432
1435
|
let hasFiles = false
|
|
@@ -1439,7 +1442,7 @@ export async function request (route, data, event, isLoading) {
|
|
|
1439
1442
|
|
|
1440
1443
|
// If yes, convert to form data
|
|
1441
1444
|
/** @type {FormData|undefined} */
|
|
1442
|
-
const formData2 = hasFiles ? formData(data, { allowEmptyArrays: true, indices: true }) : undefined
|
|
1445
|
+
const formData2 = hasFiles ? formData({ ...data }, { allowEmptyArrays: true, indices: true }) : undefined
|
|
1443
1446
|
|
|
1444
1447
|
// send the request
|
|
1445
1448
|
const axiosPromise = (method === 'get' || method === 'delete')
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
// todo: finish tailwind conversion
|
|
3
|
-
import * as util from 'nitro-web/util'
|
|
4
|
-
import SvgTick from 'nitro-web/client/imgs/icons/tick.svg'
|
|
5
|
-
import { Button, FormError, Field, Modal, Topbar, Tabbar } from 'nitro-web'
|
|
6
|
-
|
|
7
|
-
export function SettingsAccount() {
|
|
8
|
-
const isLoading = useState(false)
|
|
9
|
-
const [removeModal, setRemoveModal] = useState()
|
|
10
|
-
const [{user}, setStore] = sharedStore.useTracked()
|
|
11
|
-
const [state, setState] = useState({
|
|
12
|
-
avatar: user.avatar || '',
|
|
13
|
-
email: user.email || '',
|
|
14
|
-
firstName: user.firstName || '',
|
|
15
|
-
lastName: user.lastName || '',
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
async function onSubmit (e) {
|
|
19
|
-
try {
|
|
20
|
-
const res = await util.request(`put /api/user/${user._id}?files=true`, state, e, isLoading)
|
|
21
|
-
setStore((s) => ({ ...s, user: { ...s.user, ...res }, message: 'Saved successfully 👍️' }))
|
|
22
|
-
} catch (errors) {
|
|
23
|
-
return setState({ ...state, errors })
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div css={style}>
|
|
29
|
-
<Topbar
|
|
30
|
-
title={<>Settings</>}
|
|
31
|
-
submenu={
|
|
32
|
-
<Tabbar class="is-underline"tabs={[
|
|
33
|
-
{ label: 'Business', path: '/settings/business' },
|
|
34
|
-
{ label: 'Team', path: '/settings/team' },
|
|
35
|
-
{ label: 'Account', path: '/settings/account' },
|
|
36
|
-
]} />
|
|
37
|
-
}
|
|
38
|
-
btns={
|
|
39
|
-
<Button onClick={onSubmit} color="primary-sm" size="wide" IconLeft={SvgTick} isLoading={isLoading[0]}>
|
|
40
|
-
Save Settings
|
|
41
|
-
</Button>
|
|
42
|
-
}
|
|
43
|
-
/>
|
|
44
|
-
<div class="box p-box">
|
|
45
|
-
<h3 class="h3">Account Info</h3>
|
|
46
|
-
|
|
47
|
-
<form class="form" onSubmit={onSubmit}>
|
|
48
|
-
<div class="cols cols-6 cols-gap-3">
|
|
49
|
-
<div class="col">
|
|
50
|
-
<label for="firstName">First Name(s)</label>
|
|
51
|
-
<Field name="firstName" placeholder="E.g. Bruce" state={state} onChange={(e) => onChange(setState, e)} />
|
|
52
|
-
</div>
|
|
53
|
-
<div class="col">
|
|
54
|
-
<label for="lastName">Last Name</label>
|
|
55
|
-
<Field name="lastName" placeholder="E.g. Wayne" state={state} onChange={(e) => onChange(setState, e)} />
|
|
56
|
-
</div>
|
|
57
|
-
<div class="col">
|
|
58
|
-
<label for="email">Email Address</label>
|
|
59
|
-
<Field name="email" type="email" placeholder="Your email address..." state={state}
|
|
60
|
-
onChange={(e) => onChange(setState, e)} />
|
|
61
|
-
</div>
|
|
62
|
-
<div class="col">
|
|
63
|
-
<Link to="/reset" class="label-right link2 underline2 is-active">Reset Password?</Link>
|
|
64
|
-
<label for="password">Password</label>
|
|
65
|
-
<Field name="password" placeholder="•••••••••••" disabled={true} />
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
<div class="py-0-5 mb-12">
|
|
70
|
-
Warning: to remove all your data and delete your
|
|
71
|
-
account, <a href="#" onClick={() => setRemoveModal(user)} class="link2 underline2 is-active">click here</a>.
|
|
72
|
-
<FormError state={state} class="pt-2" />
|
|
73
|
-
</div>
|
|
74
|
-
</form>
|
|
75
|
-
</div>
|
|
76
|
-
|
|
77
|
-
<RemoveModal show={removeModal} setShow={setRemoveModal} />
|
|
78
|
-
</div>
|
|
79
|
-
)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function RemoveModal ({ show, setShow }) {
|
|
83
|
-
// @param {object} showModal - user
|
|
84
|
-
const navigate = useNavigate()
|
|
85
|
-
const isLoading = useState(false)
|
|
86
|
-
const [, setStore] = sharedStore.useTracked()
|
|
87
|
-
const [state, setState] = useState({})
|
|
88
|
-
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
if (show?._id) setState({ _id: show._id })
|
|
91
|
-
}, [show?._id])
|
|
92
|
-
|
|
93
|
-
async function onSubmit (e) {
|
|
94
|
-
try {
|
|
95
|
-
await util.request(`delete /api/account/${state._id}`, null, e, isLoading)
|
|
96
|
-
close()
|
|
97
|
-
setStore(o => ({ ...o, message: 'Data deleted successfully, Goodbye 👋...' }))
|
|
98
|
-
setTimeout(() => navigate('/signout'), 6000) // wait for setStore
|
|
99
|
-
} catch (errors) {
|
|
100
|
-
return setState({ ...state, errors })
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function close() {
|
|
105
|
-
setShow(false)
|
|
106
|
-
setTimeout(() => setState(false), 300)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<Modal show={show} setShow={close} css={style} class="p-modal-small" maxWidth={560} minHeight={0}>
|
|
111
|
-
<h2 class="h2"><em>Delete</em> Your Account?</h2>
|
|
112
|
-
<p class="text-paragraph py-2">
|
|
113
|
-
This will remove all the data against your account and including all companies owned by you.<br/>
|
|
114
|
-
<br/>
|
|
115
|
-
<b>Warning:</b> This cannot be undone.
|
|
116
|
-
</p>
|
|
117
|
-
<form class="form" onSubmit={onSubmit}>
|
|
118
|
-
<div class="py-0-5 mb-4">
|
|
119
|
-
<FormError state={state} class="pt-2" />
|
|
120
|
-
</div>
|
|
121
|
-
<Button onClick={onSubmit} color="secondary-sm" isLoading={isLoading[0]}>
|
|
122
|
-
Delete Account
|
|
123
|
-
</Button>
|
|
124
|
-
</form>
|
|
125
|
-
</Modal>
|
|
126
|
-
)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
import { css } from 'twin.macro'
|
|
130
|
-
const style = css`
|
|
131
|
-
/* input[type='file'] {
|
|
132
|
-
padding: 8px 18px;
|
|
133
|
-
font-size: 12px;
|
|
134
|
-
}
|
|
135
|
-
.avatar {
|
|
136
|
-
width: 38px;
|
|
137
|
-
height: 38px;
|
|
138
|
-
} */
|
|
139
|
-
`
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
//@ts-nocheck
|
|
2
|
-
// todo: finish tailwind conversio
|
|
3
|
-
|
|
4
|
-
////// look at the select type error below
|
|
5
|
-
import * as util from 'nitro-web/util'
|
|
6
|
-
import SvgTick from 'nitro-web/client/imgs/icons/tick.svg'
|
|
7
|
-
import { Button, Field, Select, Topbar, Tabbar, injectedConfig } from 'nitro-web'
|
|
8
|
-
|
|
9
|
-
export function SettingsBusiness() {
|
|
10
|
-
const isLoading = useState(false)
|
|
11
|
-
const [{ user }, setStore] = sharedStore.useTracked()
|
|
12
|
-
const [state, setState] = useState(() => {
|
|
13
|
-
const company = user.company
|
|
14
|
-
return {
|
|
15
|
-
business: {
|
|
16
|
-
address: company.business.address?.full || '',
|
|
17
|
-
country: company.business.country || 'nz',
|
|
18
|
-
currency: company.business.currency || 'nzd',
|
|
19
|
-
name: company.business.name || '',
|
|
20
|
-
number: company.business.number || '',
|
|
21
|
-
phone: company.business.phone || '',
|
|
22
|
-
website: company.business.website || '',
|
|
23
|
-
},
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
async function onSubmit (e) {
|
|
28
|
-
try {
|
|
29
|
-
const company = await util.request(`put /api/company/${user.company._id}`, state, e, isLoading)
|
|
30
|
-
setStore((s) => ({ ...s, user: { ...s.user, company }, message: 'Saved successfully 👍️' }))
|
|
31
|
-
} catch (errors) {
|
|
32
|
-
console.log(errors)
|
|
33
|
-
return setState({ ...state, errors })
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<div>
|
|
39
|
-
<Topbar
|
|
40
|
-
title={<>Settings</>}
|
|
41
|
-
submenu={
|
|
42
|
-
<Tabbar class="is-underline" tabs={[
|
|
43
|
-
{ label: 'Business', path: '/settings/business' },
|
|
44
|
-
{ label: 'Team', path: '/settings/team' },
|
|
45
|
-
{ label: 'Account', path: '/settings/account' },
|
|
46
|
-
]} />
|
|
47
|
-
}
|
|
48
|
-
btns={
|
|
49
|
-
<Button onClick={onSubmit} color="primary-sm" size="wide" IconLeft={SvgTick} isLoading={isLoading[0]}>
|
|
50
|
-
Save Settings
|
|
51
|
-
</Button>
|
|
52
|
-
}
|
|
53
|
-
/>
|
|
54
|
-
|
|
55
|
-
<div class="box p-box">
|
|
56
|
-
<h3 class="h3">Business Settings</h3>
|
|
57
|
-
|
|
58
|
-
<form class="form" onSubmit={onSubmit}>
|
|
59
|
-
<div class="cols cols-6 cols-gap-3">
|
|
60
|
-
<div class="col">
|
|
61
|
-
<label for="business.country">Country</label>
|
|
62
|
-
<Select
|
|
63
|
-
// https://github.com/lipis/flag-icons
|
|
64
|
-
name="business.country"
|
|
65
|
-
type="country"
|
|
66
|
-
state={state}
|
|
67
|
-
options={useMemo(() => util.getCountryOptions(injectedConfig.countries), [])}
|
|
68
|
-
onChange={(e) => onChange(setState, e)}
|
|
69
|
-
/>
|
|
70
|
-
</div>
|
|
71
|
-
<div class="col">
|
|
72
|
-
<label for="business.currency">Currency</label>
|
|
73
|
-
<Select
|
|
74
|
-
name="business.currency"
|
|
75
|
-
type="country"
|
|
76
|
-
state={state}
|
|
77
|
-
options={useMemo(() => util.getCurrencyOptions(injectedConfig.currencies), [])}
|
|
78
|
-
onChange={(e) => onChange(setState, e)}
|
|
79
|
-
/>
|
|
80
|
-
</div>
|
|
81
|
-
<div class="col">
|
|
82
|
-
<label for="business.name">Trading Name</label>
|
|
83
|
-
<Field name="business.name" placeholder="E.g. Wayne Enterprises" state={state} onChange={(e) => onChange(setState, e)} />
|
|
84
|
-
</div>
|
|
85
|
-
<div class="col">
|
|
86
|
-
<Link to="#" class="label-right link2 underline2 is-active">Custom Address</Link>
|
|
87
|
-
<label for="business.address">Address (Start Typing...)</label>
|
|
88
|
-
<Field name="business.address.full" placeholder="" state={state} onChange={(e) => onChange(setState, e)} />
|
|
89
|
-
</div>
|
|
90
|
-
<div class="col">
|
|
91
|
-
<label for="business.website">Website</label>
|
|
92
|
-
<Field name="business.website" placeholder="https://" state={state} onChange={(e) => onChange(setState, e)} />
|
|
93
|
-
</div>
|
|
94
|
-
<div class="col">
|
|
95
|
-
<label for="business.phone">Mobile Number</label>
|
|
96
|
-
<Field name="business.phone" placeholder="" state={state} onChange={(e) => onChange(setState, e)} />
|
|
97
|
-
</div>
|
|
98
|
-
<div class="col">
|
|
99
|
-
<Link to="#" class="label-right link2 underline2 is-active">What's this for?</Link>
|
|
100
|
-
<label for="tax.number">GST Number</label>
|
|
101
|
-
<Field class="mb-0" name="tax.number" placeholder="Appears on your documents" state={state}
|
|
102
|
-
onChange={(e) => onChange(setState, e)} />
|
|
103
|
-
</div>
|
|
104
|
-
<div class="col">
|
|
105
|
-
<Link to="#" class="label-right link2 underline2 is-active">What's this for?</Link>
|
|
106
|
-
<label for="business.number">NZBN</label>
|
|
107
|
-
<Field class="mb-0" name="business.number" type="text" rows="23" placeholder="Appears on your documents" state={state}
|
|
108
|
-
onChange={(e) => onChange(setState, e)} />
|
|
109
|
-
</div>
|
|
110
|
-
</div>
|
|
111
|
-
</form>
|
|
112
|
-
</div>
|
|
113
|
-
|
|
114
|
-
</div>
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
// todo: finish tailwind conversion
|
|
3
|
-
import { Button, FormError, Field, Modal, Select, injectedConfig } from 'nitro-web'
|
|
4
|
-
import SvgTick from 'nitro-web/client/imgs/icons/tick.svg'
|
|
5
|
-
|
|
6
|
-
type SettingsTeamMemberProps = {
|
|
7
|
-
showModal: boolean
|
|
8
|
-
setShowModal: (showModal: boolean) => void
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function SettingsTeamMember ({ showModal, setShowModal }: SettingsTeamMemberProps) {
|
|
12
|
-
// @param {object} showModal - user
|
|
13
|
-
const [{ user }] = sharedStore.useTracked()
|
|
14
|
-
const [isLoading] = useState(false)
|
|
15
|
-
const [state, setState] = useState({
|
|
16
|
-
business: {
|
|
17
|
-
name: '',
|
|
18
|
-
address: '',
|
|
19
|
-
website: '',
|
|
20
|
-
phone: '',
|
|
21
|
-
},
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
// permit polit changes
|
|
25
|
-
// typescripty,
|
|
26
|
-
|
|
27
|
-
function onSubmit(_e) {
|
|
28
|
-
//... save
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<Modal show={showModal} setShow={setShowModal} class="p-modal">
|
|
33
|
-
|
|
34
|
-
<h2 class="h2"><em>Add</em> Team Member</h2>
|
|
35
|
-
<p class="subtitle">Invite a new team member to collaborate with you on {injectedConfig?.name || 'Nitro'}.</p>
|
|
36
|
-
|
|
37
|
-
<form class="form" onSubmit={onSubmit}>
|
|
38
|
-
<div class="cols cols-6 cols-gap-2-5">
|
|
39
|
-
<div class="col">
|
|
40
|
-
<label for="role">Member Role</label>
|
|
41
|
-
<Select
|
|
42
|
-
name="role"
|
|
43
|
-
isSearchable={false}
|
|
44
|
-
placeholder="Select a role"
|
|
45
|
-
onChange={(e) => onChange(setState, e)}
|
|
46
|
-
state={state}
|
|
47
|
-
minMenuWidth={460}
|
|
48
|
-
options={[
|
|
49
|
-
{
|
|
50
|
-
className: 'bb',
|
|
51
|
-
value: 'owner',
|
|
52
|
-
labelControl: 'Owner',
|
|
53
|
-
label: <>
|
|
54
|
-
<div class="mb-0-5"><b>Owner</b></div>
|
|
55
|
-
<div>Full access.</div>
|
|
56
|
-
</>,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
className: 'bb',
|
|
60
|
-
value: 'manager',
|
|
61
|
-
labelControl: 'Manager',
|
|
62
|
-
label: <>
|
|
63
|
-
<div class="mb-0-5"><b>Manager</b></div>
|
|
64
|
-
<div>No access to billing or the ability to remove your account.</div>
|
|
65
|
-
</>,
|
|
66
|
-
},
|
|
67
|
-
]}
|
|
68
|
-
/>
|
|
69
|
-
</div>
|
|
70
|
-
<div class="col">
|
|
71
|
-
<label for="email">Email Address</label>
|
|
72
|
-
<Field
|
|
73
|
-
name="email" type="email" placeholder="Your email address..." state={state}
|
|
74
|
-
onChange={(e) => onChange(setState, e)}
|
|
75
|
-
/>
|
|
76
|
-
</div>
|
|
77
|
-
<div class="col">
|
|
78
|
-
<label for="firstName">First Name</label>
|
|
79
|
-
<Field name="firstName" placeholder="E.g. Bruce" state={state} onChange={(e) => onChange(setState, e)} />
|
|
80
|
-
</div>
|
|
81
|
-
<div class="col">
|
|
82
|
-
<label for="lastName">Last Name</label>
|
|
83
|
-
<Field name="lastName" placeholder="E.g. Wayne" state={state} onChange={(e) => onChange(setState, e)} />
|
|
84
|
-
</div>
|
|
85
|
-
<div class="col-12">
|
|
86
|
-
<label for="message">Invitation Message</label>
|
|
87
|
-
<Field
|
|
88
|
-
name="message"
|
|
89
|
-
type="textarea"
|
|
90
|
-
placeholder={`${user.firstName} is inviting you to collaborate on ${injectedConfig?.name || 'Nitro'}.`}
|
|
91
|
-
state={state}
|
|
92
|
-
onChange={(e) => onChange(setState, e)}
|
|
93
|
-
/>
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
<div class="py-0-5 mb-4">
|
|
98
|
-
<FormError state={state} class="pt-2" />
|
|
99
|
-
</div>
|
|
100
|
-
<Button onClick={onSubmit} color="primary-sm" IconLeft={SvgTick} isLoading={isLoading[0]}>
|
|
101
|
-
Send Invitation
|
|
102
|
-
</Button>
|
|
103
|
-
</form>
|
|
104
|
-
</Modal>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
// todo: finish tailwind conversion
|
|
3
|
-
import * as util from 'nitro-web/util'
|
|
4
|
-
import SvgPlus from 'nitro-web/client/imgs/icons/plus.svg'
|
|
5
|
-
import { Button, Table, Avatar, Tabbar, Topbar, SettingsTeamMember, injectedConfig } from 'nitro-web'
|
|
6
|
-
|
|
7
|
-
export function SettingsTeam() {
|
|
8
|
-
const isLoading = useState(false)
|
|
9
|
-
const [showModal, setShowModal] = useState()
|
|
10
|
-
const [{ user }] = sharedStore.useTracked()
|
|
11
|
-
const [state] = useState({
|
|
12
|
-
users: user?.company?.users || [],
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
function addTeamMember() {
|
|
16
|
-
//... open modal
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<div>
|
|
21
|
-
<Topbar
|
|
22
|
-
title={<>Settings</>}
|
|
23
|
-
submenu={
|
|
24
|
-
<Tabbar class="is-underline" tabs={[
|
|
25
|
-
{ label: 'Business', path: '/settings/business' },
|
|
26
|
-
{ label: 'Team', path: '/settings/team' },
|
|
27
|
-
{ label: 'Account', path: '/settings/account' },
|
|
28
|
-
]} />
|
|
29
|
-
}
|
|
30
|
-
btns={
|
|
31
|
-
<Button onClick={addTeamMember} color="primary-sm" IconLeft={SvgPlus} isLoading={isLoading[0]}>
|
|
32
|
-
Add Team Member
|
|
33
|
-
</Button>
|
|
34
|
-
}
|
|
35
|
-
/>
|
|
36
|
-
|
|
37
|
-
<Table
|
|
38
|
-
columns={[
|
|
39
|
-
{ label: 'Member\'s Name', key: 'name', width: 1 },
|
|
40
|
-
{ label: 'Email', key: 'email' },
|
|
41
|
-
{ label: 'Joined On', key: 'joinedOn', align: 'center' },
|
|
42
|
-
{ label: 'Role', key: 'role', width: '110px' },
|
|
43
|
-
]}
|
|
44
|
-
rowOnClick={(e, user) => setShowModal(user)}
|
|
45
|
-
rows={
|
|
46
|
-
state.users.map(user => ({
|
|
47
|
-
...user,
|
|
48
|
-
key: user._id,
|
|
49
|
-
name: (
|
|
50
|
-
<>
|
|
51
|
-
<Avatar awsUrl={injectedConfig.awsUrl} user={user} isRound={true} class="mt--1 mb--1" />
|
|
52
|
-
<b>{util.ucFirst(user.name)}</b>
|
|
53
|
-
{user.status != 'invited' && <span class="text-grey">(Invitation pending)</span>}
|
|
54
|
-
</>
|
|
55
|
-
),
|
|
56
|
-
joinedOn: user.status == 'invited' ? <a href="#">Resend Invite</a> : util.date(user.createdAt),
|
|
57
|
-
role: util.ucFirst(user.role),
|
|
58
|
-
}))
|
|
59
|
-
}
|
|
60
|
-
actions={[
|
|
61
|
-
{ label: 'Remove', onClick: (_row, _i) => console.log('remove') },
|
|
62
|
-
]}
|
|
63
|
-
actionsAll={[
|
|
64
|
-
{ label: 'Remove All', onClick: () => console.log('remove all') },
|
|
65
|
-
]}
|
|
66
|
-
/>
|
|
67
|
-
|
|
68
|
-
{/* Member modal */}
|
|
69
|
-
<SettingsTeamMember showModal={showModal} setShowModal={setShowModal} />
|
|
70
|
-
</div>
|
|
71
|
-
)
|
|
72
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import db from 'monastery'
|
|
3
|
-
|
|
4
|
-
export const routes = {
|
|
5
|
-
'put /api/company/:cid': ['isCompanyUser', update],
|
|
6
|
-
'put /api/user/:uid': ['isUser', updateUser],
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
async function update(req, res) {
|
|
10
|
-
try {
|
|
11
|
-
const update = await db.company.update({
|
|
12
|
-
query: req.params.cid,
|
|
13
|
-
data: req.body,
|
|
14
|
-
files: req.query.files ? req.files : undefined,
|
|
15
|
-
})
|
|
16
|
-
if (!update) {
|
|
17
|
-
throw new Error('Coudln\'t find the company to update')
|
|
18
|
-
}
|
|
19
|
-
const company = await db.company.findOne({
|
|
20
|
-
query: req.params.cid,
|
|
21
|
-
populate: db.company.loginPopulate(),
|
|
22
|
-
_privateData: true,
|
|
23
|
-
})
|
|
24
|
-
res.json(company)
|
|
25
|
-
|
|
26
|
-
} catch (errs) {
|
|
27
|
-
res.error(errs)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function updateUser(req, res) {
|
|
32
|
-
try {
|
|
33
|
-
const update = await db.user.update({
|
|
34
|
-
query: req.params.uid,
|
|
35
|
-
data: req.body,
|
|
36
|
-
files: req.query.files ? req.files : undefined,
|
|
37
|
-
})
|
|
38
|
-
if (!update) {
|
|
39
|
-
throw new Error('Coudln\'t find the user to update')
|
|
40
|
-
}
|
|
41
|
-
const user = await db.user.findOne({
|
|
42
|
-
query: req.params.uid,
|
|
43
|
-
_privateData: true,
|
|
44
|
-
blacklist: ['company'], // don't return the company id
|
|
45
|
-
})
|
|
46
|
-
res.json(user)
|
|
47
|
-
|
|
48
|
-
} catch (errs) {
|
|
49
|
-
res.error(errs)
|
|
50
|
-
}
|
|
51
|
-
}
|