nitro-web 0.0.83 → 0.0.85
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 +4 -2
- package/client/index.ts +0 -4
- package/components/auth/auth.api.js +9 -11
- 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/server/router.js +41 -1
- 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
|
|
|
@@ -291,9 +293,9 @@ async function beforeApp(config: Config) {
|
|
|
291
293
|
|
|
292
294
|
const defaultMiddleware = {
|
|
293
295
|
// Global middleware that can referenced from component routes
|
|
294
|
-
isAdmin: (route: unknown, store: { user?: { type?: string } }) => {
|
|
296
|
+
isAdmin: (route: unknown, store: { user?: { type?: string, isAdmin?: boolean } }) => {
|
|
295
297
|
const user = store.user || { type: 'visitor' }
|
|
296
|
-
if (user?.type?.match(/admin/)) return
|
|
298
|
+
if (user?.type?.match(/admin/) || user?.isAdmin) return
|
|
297
299
|
else if (user?.type && user?.type !== 'visitor') return { redirect: '/signin?unauth' }
|
|
298
300
|
else return { redirect: '/signin?signin' }
|
|
299
301
|
},
|
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'
|
|
@@ -192,7 +192,7 @@ async function resetPassword(req, res) {
|
|
|
192
192
|
async function inviteInstructions(req, res) {
|
|
193
193
|
try {
|
|
194
194
|
// Check if user is admin here rather than in middleware (which may not exist yet)
|
|
195
|
-
if (req.user.type != 'admin') {
|
|
195
|
+
if (req.user.type != 'admin' && !req.user.isAdmin) {
|
|
196
196
|
throw new Error('You are not authorized to invite users.')
|
|
197
197
|
}
|
|
198
198
|
const inviteToken = await tokenCreate()
|
|
@@ -315,7 +315,7 @@ export async function signinAndGetStore(user, isDesktop, getStore) {
|
|
|
315
315
|
return { ...store, jwt }
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
export async function userCreate({
|
|
318
|
+
export async function userCreate({ business, password, ...userDataProp }) {
|
|
319
319
|
try {
|
|
320
320
|
if (!this.findUserFromProvider) {
|
|
321
321
|
throw new Error('this.findUserFromProvider doesn\'t exist, make sure the context is available when calling this function')
|
|
@@ -330,14 +330,15 @@ export async function userCreate({ name, business, email, password }) {
|
|
|
330
330
|
users: [{ _id: userId, role: 'owner', status: 'active' }],
|
|
331
331
|
}
|
|
332
332
|
const userData = {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
333
|
+
...userDataProp,
|
|
334
|
+
_id: userId,
|
|
335
|
+
...(userDataProp.name ? {
|
|
336
|
+
firstName: fullNameSplit(userDataProp.name)[0],
|
|
337
|
+
lastName: fullNameSplit(userDataProp.name)[1],
|
|
338
|
+
} : {}),
|
|
337
339
|
password: password ? await bcrypt.hash(password, 10) : undefined,
|
|
338
340
|
...(isMultiTenant ? { company: companyData._id } : {}),
|
|
339
341
|
}
|
|
340
|
-
|
|
341
342
|
// First validate the data so we don't have to create a transaction
|
|
342
343
|
const results = await Promise.allSettled([
|
|
343
344
|
db.user.validate(userData, options),
|
|
@@ -362,10 +363,7 @@ export async function userCreate({ name, business, email, password }) {
|
|
|
362
363
|
|
|
363
364
|
} catch (err) {
|
|
364
365
|
if (!isArray(err)) throw err
|
|
365
|
-
throw err
|
|
366
|
-
if (o.title == 'firstName') o.title = 'name'
|
|
367
|
-
return o
|
|
368
|
-
})
|
|
366
|
+
else throw err //...
|
|
369
367
|
}
|
|
370
368
|
}
|
|
371
369
|
|
|
@@ -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.85",
|
|
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/server/router.js
CHANGED
|
@@ -298,8 +298,8 @@ function resolveMiddleware (controllers, middleware, route, item) {
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
export const middleware = {
|
|
301
|
+
// Default middleware called before all /api/* routes
|
|
301
302
|
order: [
|
|
302
|
-
// Express middleware runtime order that are called for all API requests
|
|
303
303
|
'loadAssets',
|
|
304
304
|
'modifyRequest',
|
|
305
305
|
'parseUrlEncoded',
|
|
@@ -308,6 +308,8 @@ export const middleware = {
|
|
|
308
308
|
'beforeAPIRoute',
|
|
309
309
|
],
|
|
310
310
|
|
|
311
|
+
// --- Default middleware ---------------------
|
|
312
|
+
|
|
311
313
|
modifyRequest: (req, res, next) => {
|
|
312
314
|
// Handy boolean denoting that the request wants JSON returned
|
|
313
315
|
// global.start = new Date().getTime()
|
|
@@ -345,4 +347,42 @@ export const middleware = {
|
|
|
345
347
|
res.set('version', req.version)
|
|
346
348
|
next()
|
|
347
349
|
},
|
|
350
|
+
|
|
351
|
+
// --- Custom middleware ----------------------
|
|
352
|
+
|
|
353
|
+
isAdmin: (req, res, next) => {
|
|
354
|
+
// Still need to remove cookie matching in favour of uid..
|
|
355
|
+
// E.g. Cookie matching handy for rare issues, e.g. signout > signin (to a different user on another tab)
|
|
356
|
+
const user = req.user
|
|
357
|
+
let cookieMatch = user && (!req.headers.authid || user._id.toString() == req.headers.authid)
|
|
358
|
+
if (cookieMatch && (user.type?.match(/admin/) || user.isAdmin)) next()
|
|
359
|
+
else if (user && (user.type?.match(/admin/) || user.isAdmin)) res.unauthorized('Invalid cookie, please refresh your browser')
|
|
360
|
+
else if (user) res.unauthorized('You are not authorised to make this request.')
|
|
361
|
+
else res.unauthorized('Please sign in first.')
|
|
362
|
+
},
|
|
363
|
+
isCompanyOwner: (req, res, next) => {
|
|
364
|
+
let user = req.user || { companies: [] }
|
|
365
|
+
let cid = req.params.cid
|
|
366
|
+
let company = user.companies.find((o) => o._id.toString() == cid)
|
|
367
|
+
let companyUser = company?.users?.find((o) => o._id.toString() == user._id.toString())
|
|
368
|
+
if (!user._id) return res.unauthorized('Please sign in first.')
|
|
369
|
+
else if (!company || !companyUser) res.unauthorized('You are not authorised to make this request.')
|
|
370
|
+
else if (companyUser.type != 'owner') res.unauthorized('Only owners can make this request.')
|
|
371
|
+
else next()
|
|
372
|
+
},
|
|
373
|
+
isCompanyUser: (req, res, next) => {
|
|
374
|
+
let user = req.user || { companies: [] }
|
|
375
|
+
let cid = req.params.cid
|
|
376
|
+
let company = user.companies.find((o) => o._id.toString() == cid)
|
|
377
|
+
if (!user._id) return res.unauthorized('Please sign in first.')
|
|
378
|
+
else if (!company) res.unauthorized('You are not authorised to make this request.')
|
|
379
|
+
else next()
|
|
380
|
+
},
|
|
381
|
+
isUser: (req, res, next) => {
|
|
382
|
+
// todo: need to double check that uid is always defined
|
|
383
|
+
let uid = req.params.uid
|
|
384
|
+
if (req.user && (typeof uid == 'undefined' || req.user._id.toString() == uid)) next()
|
|
385
|
+
else if (req.user) res.unauthorized('You are not authorised to make this request.')
|
|
386
|
+
else res.unauthorized('Please sign in first.')
|
|
387
|
+
},
|
|
348
388
|
}
|
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
|
-
}
|