nitro-web 0.0.1

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.
Files changed (152) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintrc.json +86 -0
  3. package/_example/.env-example +16 -0
  4. package/_example/client/config.ts +5 -0
  5. package/_example/client/css/index.css +35 -0
  6. package/_example/client/fonts/Roboto-Bold.ttf +0 -0
  7. package/_example/client/fonts/Roboto-BoldItalic.ttf +0 -0
  8. package/_example/client/fonts/Roboto-Italic.ttf +0 -0
  9. package/_example/client/fonts/Roboto-Medium.ttf +0 -0
  10. package/_example/client/fonts/Roboto-MediumItalic.ttf +0 -0
  11. package/_example/client/fonts/Roboto-Regular.ttf +0 -0
  12. package/_example/client/fonts/inter-v13-latin-300.woff2 +0 -0
  13. package/_example/client/fonts/inter-v13-latin-500.woff2 +0 -0
  14. package/_example/client/fonts/inter-v13-latin-600.woff2 +0 -0
  15. package/_example/client/fonts/inter-v13-latin-700.woff2 +0 -0
  16. package/_example/client/fonts/inter-v13-latin-800.woff2 +0 -0
  17. package/_example/client/fonts/inter-v13-latin-900.woff2 +0 -0
  18. package/_example/client/fonts/inter-v13-latin-regular.woff2 +0 -0
  19. package/_example/client/imgs/android-chrome-512x512.png +0 -0
  20. package/_example/client/imgs/favicon.png +0 -0
  21. package/_example/client/imgs/icons/calendar.svg +3 -0
  22. package/_example/client/imgs/icons/email.svg +6 -0
  23. package/_example/client/imgs/icons/eye-open.svg +4 -0
  24. package/_example/client/imgs/icons/eye.svg +5 -0
  25. package/_example/client/imgs/icons/filter.svg +7 -0
  26. package/_example/client/imgs/icons/left-circle.svg +3 -0
  27. package/_example/client/imgs/icons/left.svg +3 -0
  28. package/_example/client/imgs/icons/line-options.svg +5 -0
  29. package/_example/client/imgs/icons/line.svg +3 -0
  30. package/_example/client/imgs/icons/person.svg +7 -0
  31. package/_example/client/imgs/icons/plus-circle.svg +5 -0
  32. package/_example/client/imgs/icons/plus.svg +5 -0
  33. package/_example/client/imgs/icons/right-circle.svg +3 -0
  34. package/_example/client/imgs/icons/right.svg +3 -0
  35. package/_example/client/imgs/icons/search.svg +3 -0
  36. package/_example/client/imgs/icons/shield.svg +6 -0
  37. package/_example/client/imgs/icons/tick-circle-solid.svg +8 -0
  38. package/_example/client/imgs/icons/tick-circle.svg +6 -0
  39. package/_example/client/imgs/icons/tick.svg +5 -0
  40. package/_example/client/imgs/icons/up2-small.svg +4 -0
  41. package/_example/client/imgs/icons/up2.svg +4 -0
  42. package/_example/client/imgs/icons/updown.svg +6 -0
  43. package/_example/client/imgs/icons/v-big-dark.svg +3 -0
  44. package/_example/client/imgs/icons/v-dark.svg +3 -0
  45. package/_example/client/imgs/icons/v.svg +3 -0
  46. package/_example/client/imgs/icons/v2-active.svg +6 -0
  47. package/_example/client/imgs/icons/x1.svg +4 -0
  48. package/_example/client/imgs/logo/logo-white.svg +20 -0
  49. package/_example/client/imgs/logo/logo.svg +20 -0
  50. package/_example/client/imgs/no-image.jpg +0 -0
  51. package/_example/client/imgs/user.jpg +0 -0
  52. package/_example/client/index.html +12 -0
  53. package/_example/client/index.ts +47 -0
  54. package/_example/components/auth.api.js +1 -0
  55. package/_example/components/index.tsx +225 -0
  56. package/_example/components/partials/layouts.tsx +5 -0
  57. package/_example/components/settings.api.js +1 -0
  58. package/_example/server/config.js +120 -0
  59. package/_example/server/email/welcome.html +27 -0
  60. package/_example/server/index.js +32 -0
  61. package/_example/tailwind.config.js +84 -0
  62. package/_example/tsconfig.json +32 -0
  63. package/_example/types.d.ts +7 -0
  64. package/_example/webpack.config.js +4 -0
  65. package/client/app.js +300 -0
  66. package/client/css/components.css +84 -0
  67. package/client/css/fonts.css +67 -0
  68. package/client/imgs/icons/calendar.svg +3 -0
  69. package/client/imgs/icons/email.svg +6 -0
  70. package/client/imgs/icons/eye-open.svg +4 -0
  71. package/client/imgs/icons/eye.svg +5 -0
  72. package/client/imgs/icons/filter.svg +7 -0
  73. package/client/imgs/icons/left-circle.svg +3 -0
  74. package/client/imgs/icons/left.svg +3 -0
  75. package/client/imgs/icons/line-options.svg +5 -0
  76. package/client/imgs/icons/line.svg +3 -0
  77. package/client/imgs/icons/person.svg +7 -0
  78. package/client/imgs/icons/plus-circle.svg +5 -0
  79. package/client/imgs/icons/plus.svg +5 -0
  80. package/client/imgs/icons/right-circle.svg +3 -0
  81. package/client/imgs/icons/right.svg +3 -0
  82. package/client/imgs/icons/search.svg +3 -0
  83. package/client/imgs/icons/shield.svg +6 -0
  84. package/client/imgs/icons/tick-circle-solid.svg +8 -0
  85. package/client/imgs/icons/tick-circle.svg +6 -0
  86. package/client/imgs/icons/tick.svg +5 -0
  87. package/client/imgs/icons/up2-small.svg +4 -0
  88. package/client/imgs/icons/up2.svg +4 -0
  89. package/client/imgs/icons/updown.svg +6 -0
  90. package/client/imgs/icons/v-big-dark.svg +3 -0
  91. package/client/imgs/icons/v-dark.svg +3 -0
  92. package/client/imgs/icons/v.svg +3 -0
  93. package/client/imgs/icons/v2-active.svg +6 -0
  94. package/client/imgs/icons/x1.svg +4 -0
  95. package/client.js +42 -0
  96. package/components/auth/auth.api.js +419 -0
  97. package/components/auth/reset.jsx +88 -0
  98. package/components/auth/signin.jsx +74 -0
  99. package/components/auth/signup.jsx +62 -0
  100. package/components/billing/stripe.api.js +267 -0
  101. package/components/partials/element/accordion.jsx +82 -0
  102. package/components/partials/element/avatar.jsx +28 -0
  103. package/components/partials/element/button.jsx +66 -0
  104. package/components/partials/element/dropdown.jsx +185 -0
  105. package/components/partials/element/initials.jsx +56 -0
  106. package/components/partials/element/message.jsx +124 -0
  107. package/components/partials/element/modal.jsx +229 -0
  108. package/components/partials/element/sidebar.jsx +166 -0
  109. package/components/partials/element/tooltip.jsx +146 -0
  110. package/components/partials/element/topbar.jsx +25 -0
  111. package/components/partials/form/checkbox.jsx +74 -0
  112. package/components/partials/form/drop-handler.jsx +62 -0
  113. package/components/partials/form/drop.jsx +125 -0
  114. package/components/partials/form/form-error.jsx +21 -0
  115. package/components/partials/form/input-color.jsx +77 -0
  116. package/components/partials/form/input-currency.jsx +133 -0
  117. package/components/partials/form/input-date.jsx +223 -0
  118. package/components/partials/form/input.jsx +131 -0
  119. package/components/partials/form/location.jsx +212 -0
  120. package/components/partials/form/select.jsx +369 -0
  121. package/components/partials/form/toggle.jsx +46 -0
  122. package/components/partials/is-first-render.js +15 -0
  123. package/components/partials/layout/layout1.jsx +32 -0
  124. package/components/partials/layout/layout2.jsx +47 -0
  125. package/components/partials/not-found.jsx +7 -0
  126. package/components/partials/styleguide.jsx +252 -0
  127. package/components/settings/settings-account.jsx +143 -0
  128. package/components/settings/settings-business.jsx +121 -0
  129. package/components/settings/settings-team--member.jsx +108 -0
  130. package/components/settings/settings-team.jsx +76 -0
  131. package/components/settings/settings.api.js +54 -0
  132. package/package.json +175 -0
  133. package/readme.md +43 -0
  134. package/server/email/index.js +192 -0
  135. package/server/email/partials/email.css +153 -0
  136. package/server/email/partials/layout1.swig +92 -0
  137. package/server/email/partials/line.swig +8 -0
  138. package/server/email/partials/vert-10.swig +8 -0
  139. package/server/email/partials/vert-15.swig +8 -0
  140. package/server/email/partials/vert-20.swig +8 -0
  141. package/server/email/partials/vert-25.swig +8 -0
  142. package/server/email/partials/vert-30.swig +8 -0
  143. package/server/email/partials/vert-35.swig +8 -0
  144. package/server/email/partials/vert-50.swig +8 -0
  145. package/server/email/reset-password.html +21 -0
  146. package/server/email/welcome.html +21 -0
  147. package/server/models/company.js +76 -0
  148. package/server/models/user.js +45 -0
  149. package/server/router.js +355 -0
  150. package/server.js +20 -0
  151. package/util.js +1145 -0
  152. package/webpack.config.js +302 -0
@@ -0,0 +1,252 @@
1
+ /*eslint-disable*/
2
+ import { getCountryOptions, getCurrencyOptions, ucFirst } from '../../util.js'
3
+ import { CheckIcon } from '@heroicons/react/20/solid'
4
+ import { Input } from './form/input.jsx'
5
+ import { Drop } from './form/drop.jsx'
6
+ import { Select } from './form/select.jsx'
7
+ import { Dropdown } from './element/dropdown.jsx'
8
+ import { Button } from './element/button.jsx'
9
+ import { Checkbox } from './form/checkbox.jsx'
10
+
11
+ export function Styleguide({ config }) {
12
+ const [customerSearch, setCustomerSearch] = useState('')
13
+ const [state, setState] = useState({
14
+ address: '',
15
+ country: 'us',
16
+ currency: 'nzd', // can be commented too
17
+ // amount: 100,
18
+ // brandColor: '#F3CA5F',
19
+ firstName: 'Tony',
20
+ date: Date.now(),
21
+ errors: [
22
+ { title: 'address', detail: 'Address is required' },
23
+ { title: 'currency', detail: 'Currency is required' },
24
+ // { title: 'brandColor', detail: 'Color is required' },
25
+ ],
26
+ })
27
+
28
+ // Example of updating state
29
+ // useEffect(() => {
30
+ // setTimeout(() => {
31
+ // setState({ ...state, amount: 123456, currency: 'usd', brandColor: '#8656ED' })
32
+ // }, 2000)
33
+ // }, [])
34
+
35
+ const options = [
36
+ { label: 'Open customer preview' },
37
+ { label: 'Add a payment', isSelected: true },
38
+ { label: 'Email invoice' },
39
+ { label: 'Download' },
40
+ { label: 'Edit' },
41
+ { label: 'Copy' },
42
+ { label: 'Delete' },
43
+ ]
44
+
45
+ function onInputChange (e) {
46
+ if ((e.target.id == 'customer' || e.target.id == 'customer2') && e.target.value == '') {
47
+ setCustomerSearch('')
48
+ e.target.value = null // clear the selected value
49
+ }
50
+ setState(s => ({ ...s, [e.target.id]: e.target.value }))
51
+ }
52
+
53
+ function onCustomerSearch (search) {
54
+ setCustomerSearch(search || '')
55
+ }
56
+
57
+ return (
58
+ <div class="mb-10 text-left max-w-[1100px]">
59
+ <div class="mb-7">
60
+ <h1 class="h1 m-0">Styleguide</h1>
61
+ </div>
62
+
63
+ <h2 class="h3">Links</h2>
64
+ <div class="mb-8">
65
+ <a class="mr-2" href="#">Default</a>
66
+ <a class="underline1 is-active mr-2" href="#">Underline1</a>
67
+ <a class="underline2 is-active mr-2" href="#">Underline2</a>
68
+ </div>
69
+
70
+ <h2 class="h3">Checkboxes</h2>
71
+ <div class="grid grid-cols-3 gap-x-6">
72
+ <div>
73
+ <label for="input0">Label</label>
74
+ <Checkbox name="input0" type="checkbox" text="Checkbox" subtext="some additional text here." defaultChecked />
75
+ </div>
76
+ <div>
77
+ <label for="input1">Label</label>
78
+ <Checkbox name="input1" type="radio" text="Radio 1" subtext="some additional text here 1." id="input1-1" class="!mb-0" defaultChecked />
79
+ <Checkbox name="input1" type="radio" text="Radio 2" subtext="some additional text here 2." id="input1-2" class="!mt-0" />
80
+ </div>
81
+ <div>
82
+ <label for="input2">Label</label>
83
+ <Checkbox name="input2" type="toggle" text="Toggle" subtext="some additional text here." defaultChecked />
84
+ </div>
85
+ </div>
86
+
87
+ <h2 class="h3">Dropdowns</h2>
88
+ <div class="flex flex-wrap gap-x-6 gap-y-4 mb-8">
89
+ <div>
90
+ <Dropdown options={options} minWidth="250px">
91
+ <Button IconRight2="v" class="gap-x-3">Dropdown</Button>
92
+ </Dropdown>
93
+ </div>
94
+ <div>
95
+ <Dropdown
96
+ // menuIsOpen={true}
97
+ dir="bottom-right"
98
+ minWidth="330px"
99
+ options={[{ label: <><b>New Customer</b> / Add <b>Bruce Lee</b></>, className: 'border-bottom-with-space' }, ...options]}
100
+ >
101
+ <Button type="white" IconRight2="v" class="gap-x-3">Dropdown bottom-right</Button>
102
+ </Dropdown>
103
+ </div>
104
+ <div>
105
+ <Dropdown options={options} dir="top-left" minWidth="250px">
106
+ <Button type="white" IconRight2="v" class="gap-x-3">Dropdown top-left</Button>
107
+ </Dropdown>
108
+ </div>
109
+ </div>
110
+
111
+ <h2 class="h3">Buttons</h2>
112
+ <div class="flex flex-wrap gap-x-6 gap-y-4 mb-8">
113
+ <div><Button color="primary">primary (default)</Button></div>
114
+ <div><Button color="secondary">secondary button</Button></div>
115
+ <div><Button color="white">white button</Button></div>
116
+ <div><Button color="primary-xs">*-xs button</Button></div>
117
+ <div><Button color="primary-sm">*-sm button</Button></div>
118
+ <div><Button color="primary-md">*-md (default)</Button></div>
119
+ <div><Button color="primary-lg">*-lg button</Button></div>
120
+ <div><Button IconLeft={<CheckIcon class="size-5 -my-5 -mx-0.5" />}>IconLeft=Element</Button></div>
121
+ <div><Button IconRight="v">IconRight="v"</Button></div>
122
+ <div><Button IconRight2="v" className="w-[200px]">IconRight2="v"</Button></div>
123
+ <div><Button color="primary" isLoading>primary isLoading</Button></div>
124
+ </div>
125
+
126
+ <h2 class="h3">Selects</h2>
127
+ <div class="grid grid-cols-3 lg:grid-cols-3 gap-x-6">
128
+ <div>
129
+ <label for="action">Default</label>
130
+ <Select
131
+ // menuIsOpen={true}
132
+ name="action"
133
+ isSearchable={false}
134
+ options={[
135
+ { value: 'edit', label: 'Edit' },
136
+ { value: 'delete', label: 'Delete' },
137
+ ]}
138
+ />
139
+ </div>
140
+ <div>
141
+ <label for="multi">Mutli Select</label>
142
+ <Select
143
+ name="multi"
144
+ isMulti={true}
145
+ options={[
146
+ { value: 'edit', label: 'Edit' },
147
+ { value: 'delete', label: 'Delete' },
148
+ ]}
149
+ />
150
+ </div>
151
+ <div>
152
+ <label for="country">Countries</label>
153
+ <Select
154
+ // https://github.com/lipis/flag-icons
155
+ name="country"
156
+ type="country"
157
+ state={state}
158
+ options={useMemo(() => getCountryOptions(config.countries), [])}
159
+ onChange={onInputChange}
160
+ />
161
+ </div>
162
+ <div>
163
+ <label for="customer">List Item with Action</label>
164
+ <Select
165
+ // menuIsOpen={true}
166
+ placeholder="Select or add customer..."
167
+ name="customer"
168
+ type="customer"
169
+ state={state}
170
+ onChange={onInputChange}
171
+ onInputChange={onCustomerSearch}
172
+ options={[
173
+ {
174
+ className: 'bb',
175
+ fixed: true,
176
+ value: '',
177
+ label: <>
178
+ <b>New Customer</b>
179
+ {customerSearch ? <> / Add <b>{ucFirst(customerSearch)}</b></> : ''}
180
+ </>,
181
+ },
182
+ { value: '1', label: 'Iron Man Industries' },
183
+ { value: '2', label: 'Captain America' },
184
+ { value: '3', label: 'Thor Limited' },
185
+ ]}
186
+ />
187
+ </div>
188
+ <div>
189
+ <label for="currency">Currencies (Error)</label>
190
+ <Select
191
+ name="currency"
192
+ state={state}
193
+ options={useMemo(() => getCurrencyOptions(config.currencies), [])}
194
+ onChange={onInputChange}
195
+ />
196
+ </div>
197
+ </div>
198
+
199
+ <h2 class="h3">Inputs</h2>
200
+ <div class="grid grid-cols-3 gap-x-6 mb-4">
201
+ <div>
202
+ <label for="firstName">First Name</label>
203
+ <Input name="firstName" state={state} onChange={onInputChange} />
204
+ </div>
205
+ <div>
206
+ <label for="email">Email Address</label>
207
+ <Input name="email" type="email" placeholder="Your email address..."/>
208
+ </div>
209
+ <div>
210
+ <div class="flex justify-between">
211
+ <label for="password">Password</label>
212
+ <a href="#" class="label">Forgot?</a>
213
+ </div>
214
+ <Input name="password" type="password"/>
215
+ </div>
216
+ <div>
217
+ <label for="search">Search</label>
218
+ <Input name="search" type="search" placeholder="Search..."/>
219
+ </div>
220
+ <div>
221
+ <label for="filter">Filter</label>
222
+ <Input name="filter" type="filter" />
223
+ </div>
224
+ <div>
225
+ <label for="address">Input Error</label>
226
+ <Input name="address" type="address" placeholder="Address..." state={state} onChange={onInputChange} />
227
+ </div>
228
+ {/* <div>
229
+ <label for="date">Date</label>
230
+ <Input name="date" type="date" prefix="Date:" state={state} onChange={onInputChange} />
231
+ </div> */}
232
+ <div>
233
+ <label for="brandColor">Brand Color</label>
234
+ <Input name="brandColor" type="color" state={state} onChange={onInputChange} />
235
+ </div>
236
+ <div>
237
+ <label for="description">Description</label>
238
+ <Input name="description" type="textarea" />
239
+ </div>
240
+ <div>
241
+ <label for="amount">Amount ({state.amount})</label>
242
+ <Input name="amount" type="currency" state={state} currency={state.currency || 'nzd'} onChange={onInputChange} config={config} />
243
+ </div>
244
+ <div>
245
+ <label for="avatar">Avatar</label>
246
+ <Drop class="is-small" name="avatar" state={state} onChange={onInputChange} awsUrl={config.awsUrl} />
247
+ </div>
248
+ </div>
249
+
250
+ </div>
251
+ )
252
+ }
@@ -0,0 +1,143 @@
1
+ // todo: finish tailwind conversion
2
+ import * as util from '../../util.js'
3
+ import SvgTick from '../../client/imgs/icons/tick.svg'
4
+ import { Button } from '../partials/element/button.jsx'
5
+ import { FormError } from '../partials/form/form-error.jsx'
6
+ import { Input } from '../partials/form/input.jsx'
7
+ import { Modal } from '../partials/element/modal.jsx'
8
+ import { Topbar } from '../partials/element/topbar.jsx'
9
+ import { Tabbar } from '../partials/element/tabbar.jsx'
10
+
11
+ export function SettingsAccount() {
12
+ let isLoading = useState('')
13
+ let [removeModal, setRemoveModal] = useState()
14
+ let [{user}, setStore] = sharedStore.useTracked()
15
+ let [state, setState] = useState({
16
+ avatar: user.avatar || '',
17
+ email: user.email || '',
18
+ firstName: user.firstName || '',
19
+ lastName: user.lastName || '',
20
+ })
21
+
22
+ async function onSubmit (e) {
23
+ try {
24
+ const res = await util.request(e, `put /api/user/${user._id}?files=true`, state, isLoading)
25
+ setStore((s) => ({ ...s, user: { ...s.user, ...res }, message: 'Saved successfully 👍️' }))
26
+ } catch (errors) {
27
+ return setState({ ...state, errors })
28
+ }
29
+ }
30
+
31
+ return (
32
+ <div css={style}>
33
+ <Topbar
34
+ title={<>Settings</>}
35
+ submenu={
36
+ <Tabbar class="is-underline"tabs={[
37
+ { label: 'Business', path: '/settings/business' },
38
+ { label: 'Team', path: '/settings/team' },
39
+ { label: 'Account', path: '/settings/account' },
40
+ ]} />
41
+ }
42
+ btns={
43
+ <Button onClick={onSubmit} color="primary-sm" size="wide" IconLeft={SvgTick} isLoading={isLoading[0]}>
44
+ Save Settings
45
+ </Button>
46
+ }
47
+ />
48
+ <div class="box p-box">
49
+ <h3 class="h3">Account Info</h3>
50
+
51
+ <form class="form" onSubmit={onSubmit}>
52
+ <div class="cols cols-6 cols-gap-3">
53
+ <div class="col">
54
+ <label for="firstName">First Name(s)</label>
55
+ <Input name="firstName" placeholder="E.g. Tony" state={state} onChange={onChange(setState)} />
56
+ </div>
57
+ <div class="col">
58
+ <label for="lastName">Last Name</label>
59
+ <Input name="lastName" placeholder="E.g. Stark" state={state} onChange={onChange(setState)} />
60
+ </div>
61
+ <div class="col">
62
+ <label for="email">Email Address</label>
63
+ <Input name="email" type="email" placeholder="Your email address..." state={state}
64
+ onChange={onChange(setState)} />
65
+ </div>
66
+ <div class="col">
67
+ <Link to="/reset" class="label-right link2 underline2 is-active">Reset Password?</Link>
68
+ <label for="password">Password</label>
69
+ <Input name="password" placeholder="•••••••••••" disabled={true} />
70
+ </div>
71
+ </div>
72
+
73
+ <div class="py-0-5 mb-12">
74
+ Warning: to remove all your data and delete your
75
+ account, <a href="#" onClick={() => setRemoveModal(user)} class="link2 underline2 is-active">click here</a>.
76
+ <FormError state={state} class="pt-2" />
77
+ </div>
78
+ </form>
79
+ </div>
80
+
81
+ <RemoveModal show={removeModal} setShow={setRemoveModal} />
82
+ </div>
83
+ )
84
+ }
85
+
86
+ export function RemoveModal ({ show, setShow }) {
87
+ // @param {object} showModal - user
88
+ const navigate = useNavigate()
89
+ const isLoading = useState(false)
90
+ const [, setStore] = sharedStore.useTracked()
91
+ const [state, setState] = useState({})
92
+
93
+ useEffect(() => {
94
+ if (show?._id) setState({ _id: show._id })
95
+ }, [show?._id])
96
+
97
+ async function onSubmit (e) {
98
+ try {
99
+ await util.request(e, `delete /api/account/${state._id}`, null, isLoading)
100
+ close()
101
+ setStore(o => ({ ...o, message: 'Data deleted successfully, Goodbye 👋...' }))
102
+ setTimeout(() => navigate('/signout'), 6000) // wait for setStore
103
+ } catch (errors) {
104
+ return setState({ ...state, errors })
105
+ }
106
+ }
107
+
108
+ function close() {
109
+ setShow(false)
110
+ setTimeout(() => setState(false), 300)
111
+ }
112
+
113
+ return (
114
+ <Modal show={show} setShow={close} css={style} class="p-modal-small" maxWidth={560} minHeight={0}>
115
+ <h2 class="h2"><em>Delete</em> Your Account?</h2>
116
+ <p class="text-paragraph py-2">
117
+ This will remove all the data against your account and including all companies owned by you.<br/>
118
+ <br/>
119
+ <b>Warning:</b> This cannot be undone.
120
+ </p>
121
+ <form class="form" onSubmit={onSubmit}>
122
+ <div class="py-0-5 mb-4">
123
+ <FormError state={state} class="pt-2" />
124
+ </div>
125
+ <Button onClick={onSubmit} color="secondary-sm" isLoading={isLoading[0]}>
126
+ Delete Account
127
+ </Button>
128
+ </form>
129
+ </Modal>
130
+ )
131
+ }
132
+
133
+ import { css } from '@emotion/react'
134
+ const style = (_theme) => css`
135
+ /* input[type='file'] {
136
+ padding: 8px 18px;
137
+ font-size: 12px;
138
+ }
139
+ .avatar {
140
+ width: 38px;
141
+ height: 38px;
142
+ } */
143
+ `
@@ -0,0 +1,121 @@
1
+ // todo: finish tailwind conversion
2
+ import * as util from '../../util.js'
3
+ import SvgTick from '../../client/imgs/icons/tick.svg'
4
+ import { Button } from '../partials/element/button.jsx'
5
+ import { Input } from '../partials/form/input.jsx'
6
+ import { Select } from '../partials/form/select.jsx'
7
+ import { Topbar } from '../partials/element/topbar.jsx'
8
+ import { Tabbar } from '../partials/element/tabbar.jsx'
9
+
10
+ export function SettingsBusiness({ config }) {
11
+ const isLoading = useState('')
12
+ const [{ user }, setStore] = sharedStore.useTracked()
13
+ const [state, setState] = useState(() => {
14
+ const company = user.company
15
+ return {
16
+ business: {
17
+ address: company.business.address?.full || '',
18
+ country: company.business.country || 'nz',
19
+ currency: company.business.currency || 'nzd',
20
+ name: company.business.name || '',
21
+ number: company.business.number || '',
22
+ phone: company.business.phone || '',
23
+ website: company.business.website || '',
24
+ },
25
+ }
26
+ })
27
+
28
+ async function onSubmit (e) {
29
+ try {
30
+ const company = await util.request(e, `put /api/company/${user.company._id}`, state, isLoading)
31
+ setStore((s) => ({ ...s, user: { ...s.user, company }, message: 'Saved successfully 👍️' }))
32
+ } catch (errors) {
33
+ console.log(errors)
34
+ return setState({ ...state, errors })
35
+ }
36
+ }
37
+
38
+ return (
39
+ <div css={style}>
40
+ <Topbar
41
+ title={<>Settings</>}
42
+ submenu={
43
+ <Tabbar class="is-underline" tabs={[
44
+ { label: 'Business', path: '/settings/business' },
45
+ { label: 'Team', path: '/settings/team' },
46
+ { label: 'Account', path: '/settings/account' },
47
+ ]} />
48
+ }
49
+ btns={
50
+ <Button onClick={onSubmit} color="primary-sm" size="wide" IconLeft={SvgTick} isLoading={isLoading[0]}>
51
+ Save Settings
52
+ </Button>
53
+ }
54
+ />
55
+
56
+ <div class="box p-box">
57
+ <h3 class="h3">Business Settings</h3>
58
+
59
+ <form class="form" onSubmit={onSubmit}>
60
+ <div class="cols cols-6 cols-gap-3">
61
+ <div class="col">
62
+ <label for="business.country">Country</label>
63
+ <Select
64
+ // https://github.com/lipis/flag-icons
65
+ name="business.country"
66
+ type="country"
67
+ state={state}
68
+ options={useMemo(() => util.getCountryOptions(config.countries), [])}
69
+ onChange={onChange(setState)}
70
+ />
71
+ </div>
72
+ <div class="col">
73
+ <label for="business.currency">Currency</label>
74
+ <Select
75
+ name="business.currency"
76
+ type="country"
77
+ state={state}
78
+ options={useMemo(() => util.getCurrencyOptions(config.currencies), [])}
79
+ onChange={onChange(setState)}
80
+ />
81
+ </div>
82
+ <div class="col">
83
+ <label for="business.name">Trading Name</label>
84
+ <Input name="business.name" placeholder="E.g. Stark Industries" state={state} onChange={onChange(setState)} />
85
+ </div>
86
+ <div class="col">
87
+ <Link to="#" class="label-right link2 underline2 is-active">Custom Address</Link>
88
+ <label for="business.address">Address (Start Typing...)</label>
89
+ <Input name="business.address.full" placeholder="" state={state} onChange={onChange(setState)} />
90
+ </div>
91
+ <div class="col">
92
+ <label for="business.website">Website</label>
93
+ <Input name="business.website" placeholder="https://" state={state} onChange={onChange(setState)} />
94
+ </div>
95
+ <div class="col">
96
+ <label for="business.phone">Mobile Number</label>
97
+ <Input name="business.phone" placeholder="" state={state} onChange={onChange(setState)} />
98
+ </div>
99
+ <div class="col">
100
+ <Link to="#" class="label-right link2 underline2 is-active">What&apos;s this for?</Link>
101
+ <label for="tax.number">GST Number</label>
102
+ <Input class="mb-0" name="tax.number" placeholder="Appears on your documents" state={state}
103
+ onChange={onChange(setState)} />
104
+ </div>
105
+ <div class="col">
106
+ <Link to="#" class="label-right link2 underline2 is-active">What&apos;s this for?</Link>
107
+ <label for="business.number">NZBN</label>
108
+ <Input class="mb-0" name="business.number" placeholder="Appears on your documents" state={state}
109
+ onChange={onChange(setState)} />
110
+ </div>
111
+ </div>
112
+ </form>
113
+ </div>
114
+
115
+ </div>
116
+ )
117
+ }
118
+
119
+ import { css } from '@emotion/react'
120
+ const style = (_theme) => css`
121
+ `
@@ -0,0 +1,108 @@
1
+ // todo: finish tailwind conversion
2
+ import { css } from '@emotion/react'
3
+ import { Button } from '../partials/element/button.jsx'
4
+ import { Modal } from '../partials/element/modal.jsx'
5
+ import { FormError } from '../partials/form/form-error.jsx'
6
+ import { Input } from '../partials/form/input.jsx'
7
+ import { Select } from '../partials/form/select.jsx'
8
+ import SvgTick from '../../client/imgs/icons/tick.svg'
9
+
10
+ export function SettingsTeamMember ({ showModal, setShowModal }) {
11
+ // @param {object} showModal - user
12
+ const [{ user }] = sharedStore.useTracked()
13
+ const [isLoading] = useState('')
14
+ const [state, setState] = useState({
15
+ business: {
16
+ name: '',
17
+ address: '',
18
+ website: '',
19
+ phone: '',
20
+ },
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} css={style} 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 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={onChange(setState)}
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
+ <Input
73
+ name="email" type="email" placeholder="Your email address..." state={state}
74
+ onChange={onChange(setState)}
75
+ />
76
+ </div>
77
+ <div class="col">
78
+ <label for="firstName">First Name</label>
79
+ <Input name="firstName" placeholder="E.g. Tony" state={state} onChange={onChange(setState)} />
80
+ </div>
81
+ <div class="col">
82
+ <label for="lastName">Last Name</label>
83
+ <Input name="lastName" placeholder="E.g. Stark" state={state} onChange={onChange(setState)} />
84
+ </div>
85
+ <div class="col-12">
86
+ <label for="message">Invitation Message</label>
87
+ <Input
88
+ name="message"
89
+ type="textarea"
90
+ placeholder={`${user.firstName} is inviting you to collaborate on Nitro.`}
91
+ state={state} onChange={onChange(setState)}
92
+ />
93
+ </div>
94
+ </div>
95
+
96
+ <div class="py-0-5 mb-4">
97
+ <FormError state={state} class="pt-2" />
98
+ </div>
99
+ <Button onClick={onSubmit} color="primary-sm" IconLeft={SvgTick} isLoading={isLoading[0]}>
100
+ Send Invitation
101
+ </Button>
102
+ </form>
103
+ </Modal>
104
+ )
105
+ }
106
+
107
+ const style = (_theme) => css`
108
+ `