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.
- package/.editorconfig +9 -0
- package/.eslintrc.json +86 -0
- package/_example/.env-example +16 -0
- package/_example/client/config.ts +5 -0
- package/_example/client/css/index.css +35 -0
- package/_example/client/fonts/Roboto-Bold.ttf +0 -0
- package/_example/client/fonts/Roboto-BoldItalic.ttf +0 -0
- package/_example/client/fonts/Roboto-Italic.ttf +0 -0
- package/_example/client/fonts/Roboto-Medium.ttf +0 -0
- package/_example/client/fonts/Roboto-MediumItalic.ttf +0 -0
- package/_example/client/fonts/Roboto-Regular.ttf +0 -0
- package/_example/client/fonts/inter-v13-latin-300.woff2 +0 -0
- package/_example/client/fonts/inter-v13-latin-500.woff2 +0 -0
- package/_example/client/fonts/inter-v13-latin-600.woff2 +0 -0
- package/_example/client/fonts/inter-v13-latin-700.woff2 +0 -0
- package/_example/client/fonts/inter-v13-latin-800.woff2 +0 -0
- package/_example/client/fonts/inter-v13-latin-900.woff2 +0 -0
- package/_example/client/fonts/inter-v13-latin-regular.woff2 +0 -0
- package/_example/client/imgs/android-chrome-512x512.png +0 -0
- package/_example/client/imgs/favicon.png +0 -0
- package/_example/client/imgs/icons/calendar.svg +3 -0
- package/_example/client/imgs/icons/email.svg +6 -0
- package/_example/client/imgs/icons/eye-open.svg +4 -0
- package/_example/client/imgs/icons/eye.svg +5 -0
- package/_example/client/imgs/icons/filter.svg +7 -0
- package/_example/client/imgs/icons/left-circle.svg +3 -0
- package/_example/client/imgs/icons/left.svg +3 -0
- package/_example/client/imgs/icons/line-options.svg +5 -0
- package/_example/client/imgs/icons/line.svg +3 -0
- package/_example/client/imgs/icons/person.svg +7 -0
- package/_example/client/imgs/icons/plus-circle.svg +5 -0
- package/_example/client/imgs/icons/plus.svg +5 -0
- package/_example/client/imgs/icons/right-circle.svg +3 -0
- package/_example/client/imgs/icons/right.svg +3 -0
- package/_example/client/imgs/icons/search.svg +3 -0
- package/_example/client/imgs/icons/shield.svg +6 -0
- package/_example/client/imgs/icons/tick-circle-solid.svg +8 -0
- package/_example/client/imgs/icons/tick-circle.svg +6 -0
- package/_example/client/imgs/icons/tick.svg +5 -0
- package/_example/client/imgs/icons/up2-small.svg +4 -0
- package/_example/client/imgs/icons/up2.svg +4 -0
- package/_example/client/imgs/icons/updown.svg +6 -0
- package/_example/client/imgs/icons/v-big-dark.svg +3 -0
- package/_example/client/imgs/icons/v-dark.svg +3 -0
- package/_example/client/imgs/icons/v.svg +3 -0
- package/_example/client/imgs/icons/v2-active.svg +6 -0
- package/_example/client/imgs/icons/x1.svg +4 -0
- package/_example/client/imgs/logo/logo-white.svg +20 -0
- package/_example/client/imgs/logo/logo.svg +20 -0
- package/_example/client/imgs/no-image.jpg +0 -0
- package/_example/client/imgs/user.jpg +0 -0
- package/_example/client/index.html +12 -0
- package/_example/client/index.ts +47 -0
- package/_example/components/auth.api.js +1 -0
- package/_example/components/index.tsx +225 -0
- package/_example/components/partials/layouts.tsx +5 -0
- package/_example/components/settings.api.js +1 -0
- package/_example/server/config.js +120 -0
- package/_example/server/email/welcome.html +27 -0
- package/_example/server/index.js +32 -0
- package/_example/tailwind.config.js +84 -0
- package/_example/tsconfig.json +32 -0
- package/_example/types.d.ts +7 -0
- package/_example/webpack.config.js +4 -0
- package/client/app.js +300 -0
- package/client/css/components.css +84 -0
- package/client/css/fonts.css +67 -0
- package/client/imgs/icons/calendar.svg +3 -0
- package/client/imgs/icons/email.svg +6 -0
- package/client/imgs/icons/eye-open.svg +4 -0
- package/client/imgs/icons/eye.svg +5 -0
- package/client/imgs/icons/filter.svg +7 -0
- package/client/imgs/icons/left-circle.svg +3 -0
- package/client/imgs/icons/left.svg +3 -0
- package/client/imgs/icons/line-options.svg +5 -0
- package/client/imgs/icons/line.svg +3 -0
- package/client/imgs/icons/person.svg +7 -0
- package/client/imgs/icons/plus-circle.svg +5 -0
- package/client/imgs/icons/plus.svg +5 -0
- package/client/imgs/icons/right-circle.svg +3 -0
- package/client/imgs/icons/right.svg +3 -0
- package/client/imgs/icons/search.svg +3 -0
- package/client/imgs/icons/shield.svg +6 -0
- package/client/imgs/icons/tick-circle-solid.svg +8 -0
- package/client/imgs/icons/tick-circle.svg +6 -0
- package/client/imgs/icons/tick.svg +5 -0
- package/client/imgs/icons/up2-small.svg +4 -0
- package/client/imgs/icons/up2.svg +4 -0
- package/client/imgs/icons/updown.svg +6 -0
- package/client/imgs/icons/v-big-dark.svg +3 -0
- package/client/imgs/icons/v-dark.svg +3 -0
- package/client/imgs/icons/v.svg +3 -0
- package/client/imgs/icons/v2-active.svg +6 -0
- package/client/imgs/icons/x1.svg +4 -0
- package/client.js +42 -0
- package/components/auth/auth.api.js +419 -0
- package/components/auth/reset.jsx +88 -0
- package/components/auth/signin.jsx +74 -0
- package/components/auth/signup.jsx +62 -0
- package/components/billing/stripe.api.js +267 -0
- package/components/partials/element/accordion.jsx +82 -0
- package/components/partials/element/avatar.jsx +28 -0
- package/components/partials/element/button.jsx +66 -0
- package/components/partials/element/dropdown.jsx +185 -0
- package/components/partials/element/initials.jsx +56 -0
- package/components/partials/element/message.jsx +124 -0
- package/components/partials/element/modal.jsx +229 -0
- package/components/partials/element/sidebar.jsx +166 -0
- package/components/partials/element/tooltip.jsx +146 -0
- package/components/partials/element/topbar.jsx +25 -0
- package/components/partials/form/checkbox.jsx +74 -0
- package/components/partials/form/drop-handler.jsx +62 -0
- package/components/partials/form/drop.jsx +125 -0
- package/components/partials/form/form-error.jsx +21 -0
- package/components/partials/form/input-color.jsx +77 -0
- package/components/partials/form/input-currency.jsx +133 -0
- package/components/partials/form/input-date.jsx +223 -0
- package/components/partials/form/input.jsx +131 -0
- package/components/partials/form/location.jsx +212 -0
- package/components/partials/form/select.jsx +369 -0
- package/components/partials/form/toggle.jsx +46 -0
- package/components/partials/is-first-render.js +15 -0
- package/components/partials/layout/layout1.jsx +32 -0
- package/components/partials/layout/layout2.jsx +47 -0
- package/components/partials/not-found.jsx +7 -0
- package/components/partials/styleguide.jsx +252 -0
- package/components/settings/settings-account.jsx +143 -0
- package/components/settings/settings-business.jsx +121 -0
- package/components/settings/settings-team--member.jsx +108 -0
- package/components/settings/settings-team.jsx +76 -0
- package/components/settings/settings.api.js +54 -0
- package/package.json +175 -0
- package/readme.md +43 -0
- package/server/email/index.js +192 -0
- package/server/email/partials/email.css +153 -0
- package/server/email/partials/layout1.swig +92 -0
- package/server/email/partials/line.swig +8 -0
- package/server/email/partials/vert-10.swig +8 -0
- package/server/email/partials/vert-15.swig +8 -0
- package/server/email/partials/vert-20.swig +8 -0
- package/server/email/partials/vert-25.swig +8 -0
- package/server/email/partials/vert-30.swig +8 -0
- package/server/email/partials/vert-35.swig +8 -0
- package/server/email/partials/vert-50.swig +8 -0
- package/server/email/reset-password.html +21 -0
- package/server/email/welcome.html +21 -0
- package/server/models/company.js +76 -0
- package/server/models/user.js +45 -0
- package/server/router.js +355 -0
- package/server.js +20 -0
- package/util.js +1145 -0
- 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'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'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
|
+
`
|