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,369 @@
|
|
|
1
|
+
/*eslint-disable*/
|
|
2
|
+
import { css } from 'twin.macro'
|
|
3
|
+
import { twMerge } from 'tailwind-merge'
|
|
4
|
+
import ReactSelect, { components, createFilter } from 'react-select'
|
|
5
|
+
import { ChevronDownIcon, CheckCircleIcon, XMarkIcon } from '@heroicons/react/20/solid'
|
|
6
|
+
import * as util from '../../../util.js'
|
|
7
|
+
const filterFn = createFilter()
|
|
8
|
+
|
|
9
|
+
const selectStyles = {
|
|
10
|
+
// Based off https://www.jussivirtanen.fi/writing/styling-react-select-with-tailwind
|
|
11
|
+
// Input container
|
|
12
|
+
control: {
|
|
13
|
+
base: `rounded-md bg-white hover:cursor-pointer text-sm sm:text-sm/6 outline outline-1 -outline-offset-1 outline-input-border`,
|
|
14
|
+
focus: `outline-2 -outline-offset-2 outline-primary`,
|
|
15
|
+
error: `outline-danger`,
|
|
16
|
+
},
|
|
17
|
+
valueContainer: 'py-2 px-3 gap-1',
|
|
18
|
+
// Input container objects
|
|
19
|
+
input: {
|
|
20
|
+
base: 'text-input',
|
|
21
|
+
error: 'text-red-900',
|
|
22
|
+
},
|
|
23
|
+
multiValue: 'bg-primary text-white rounded items-center pl-2 pr-1.5 gap-1.5',
|
|
24
|
+
multiValueLabel: '',
|
|
25
|
+
multiValueRemove: `border border-primary-dark bg-white rounded-md text-dark hover:bg-red-50`,
|
|
26
|
+
placeholder: 'text-input-placeholder',
|
|
27
|
+
singleValue: {
|
|
28
|
+
base: 'text-input',
|
|
29
|
+
error: 'text-red-900',
|
|
30
|
+
},
|
|
31
|
+
// Icon indicators
|
|
32
|
+
clearIndicator: 'text-gray-500 p-1 rounded-md hover:bg-red-50 hover:text-red-800',
|
|
33
|
+
dropdownIndicator: 'p-1 hover:bg-gray-100 text-gray-500 rounded-md hover:text-black',
|
|
34
|
+
indicatorsContainer: 'p-1 px-2 gap-1',
|
|
35
|
+
indicatorSeparator: 'py-0.5 before:content-[""] before:block before:bg-gray-100 before:w-px before:h-full',
|
|
36
|
+
// Dropdown menu
|
|
37
|
+
menu: 'mt-1.5 border border-dropdown-ul-border bg-white rounded-md text-sm overflow-hidden shadow-dropdown-ul',
|
|
38
|
+
groupHeading: 'ml-3 mt-2 mb-1 text-gray-500 text-sm',
|
|
39
|
+
noOptionsMessage: 'm-1 text-gray-500 p-2 bg-gray-50 border border-dashed border-gray-200 rounded-sm',
|
|
40
|
+
option: {
|
|
41
|
+
base: 'relative px-3 py-2 !flex items-center gap-2 cursor-default',
|
|
42
|
+
hover: 'bg-gray-50',
|
|
43
|
+
selected: '!bg-gray-100 text-primary-dark',
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getSelectStyle({ name, isFocused, isSelected, hasError, usePrefixes }) {
|
|
48
|
+
// Returns a class list that conditionally includes hover/focus modifier classes, or uses CSS modifiers, e.g. hover:, focus:
|
|
49
|
+
const obj = selectStyles[name]
|
|
50
|
+
let output = obj?.base
|
|
51
|
+
if (typeof obj == 'string') return obj // no modifiers
|
|
52
|
+
|
|
53
|
+
if (usePrefixes) {
|
|
54
|
+
if (obj.focus) output += ' ' + obj.focus.split(' ').map(part => `focus:${part}`).join(' ')
|
|
55
|
+
if (obj.hover) output += ' ' + obj.hover.split(' ').map(part => `hover:${part}`).join(' ')
|
|
56
|
+
} else {
|
|
57
|
+
if (obj.focus && isFocused) output += ` ${obj.focus}`
|
|
58
|
+
if (obj.hover && isFocused) output += ` ${obj.hover}`
|
|
59
|
+
}
|
|
60
|
+
if (obj.error && hasError) output += ` ${obj.error}`
|
|
61
|
+
if (obj.selected && isSelected) output += ` ${obj.selected}`
|
|
62
|
+
|
|
63
|
+
return twMerge(output)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} name - field name or path on state (used to match errors), e.g. 'date', 'company.email'
|
|
68
|
+
* @param {string} [minMenuWidth] - width of the dropdown menu
|
|
69
|
+
* @param {string} [inputId] - name used if not provided
|
|
70
|
+
* @param {function} [onChange] - e.g. (event) => onInputChange(event)
|
|
71
|
+
* @param {object} [state] - object to get value from, and check errors against
|
|
72
|
+
* @param {string} [type] - speical types: 'country', 'customer', 'customer-big'
|
|
73
|
+
*
|
|
74
|
+
* react-select prop quick reference (https://react-select.com/props#api):
|
|
75
|
+
* isDisabled={false}
|
|
76
|
+
* isMulti={false}
|
|
77
|
+
* isSearchable={true}
|
|
78
|
+
* options={[{ value: 'chocolate', label: 'Chocolate' }]}
|
|
79
|
+
* placeholder="Select a color"
|
|
80
|
+
* value={options.find(o => o.code == state.color)} // to clear you need to set to null, not undefined
|
|
81
|
+
* isClearable={false}
|
|
82
|
+
* menuIsOpen={false}
|
|
83
|
+
*/
|
|
84
|
+
export function Select({ inputId, minMenuWidth, name, prefix='', onChange, options, state, type, ...props }) {
|
|
85
|
+
if (!name) throw new Error('Select component requires a `name` and `options` prop')
|
|
86
|
+
|
|
87
|
+
// Input is always controlled if state is passed in
|
|
88
|
+
if (props.value) {
|
|
89
|
+
var value = props.value
|
|
90
|
+
} else if (typeof state == 'object') {
|
|
91
|
+
value = options.find(o => o.value == util.deepFind(state, name))
|
|
92
|
+
if (typeof value == 'undefined') value = ''
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// An error matches this input path
|
|
96
|
+
for (let item of (state?.errors || [])) {
|
|
97
|
+
if (util.isRegex(name) && (item.title||'').match(name)) var hasError = item
|
|
98
|
+
else if (item.title == name) hasError = item
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
// classNames={{
|
|
103
|
+
// // *Same classes as input.jsx*
|
|
104
|
+
// // Based off https://www.jussivirtanen.fi/writing/styling-react-select-with-tailwind
|
|
105
|
+
// //
|
|
106
|
+
// // Input container
|
|
107
|
+
// control: ({ isFocused }) => `rounded-md bg-white hover:cursor-pointer text-sm sm:text-sm/6
|
|
108
|
+
// ${isFocused
|
|
109
|
+
// ? `outline outline-2 -outline-offset-2 ${error ? 'outline-danger' : 'outline-primary'}`
|
|
110
|
+
// : `outline outline-1 -outline-offset-1 ${error ? 'outline-danger' : 'outline-input-border'}`}`,
|
|
111
|
+
// valueContainer: () => 'py-2 px-3 gap-1',
|
|
112
|
+
// // Input container objects
|
|
113
|
+
// input: () => `${error ? 'text-red-900' : 'text-input'}`,
|
|
114
|
+
// multiValue: () => 'bg-primary text-white rounded items-center pl-2 pr-1.5 gap-1.5',
|
|
115
|
+
// multiValueLabel: () => '',
|
|
116
|
+
// multiValueRemove: () => `border border-primary-dark bg-white rounded-md text-dark hover:bg-red-50`,
|
|
117
|
+
// placeholder: () => 'text-input-placeholder',
|
|
118
|
+
// singleValue: () => `${error ? 'text-red-900' : 'text-input'}`,
|
|
119
|
+
// // Indicators
|
|
120
|
+
// clearIndicator: () =>'text-gray-500 p-1 rounded-md hover:bg-red-50 hover:text-red-800',
|
|
121
|
+
// dropdownIndicator: () => 'p-1 hover:bg-gray-100 text-gray-500 rounded-md hover:text-black',
|
|
122
|
+
// indicatorsContainer: () => 'p-1 px-2 gap-1',
|
|
123
|
+
// indicatorSeparator: () => 'py-0.5 before:content-[""] before:block before:bg-gray-100 before:w-px before:h-full',
|
|
124
|
+
// // Dropmenu
|
|
125
|
+
// menu: () => 'mt-1.5 border border-dropdown-ul-border bg-white rounded-md text-sm overflow-hidden shadow-dropdown-ul',
|
|
126
|
+
// groupHeading: () => 'ml-3 mt-2 mb-1 text-gray-500 text-sm',
|
|
127
|
+
// noOptionsMessage: () => 'm-1 text-gray-500 p-2 bg-gray-50 border border-dashed border-gray-200 rounded-sm',
|
|
128
|
+
// option: ({ isFocused, isSelected }) => `hover:cursor-pointer px-3 py-2 !flex items-center gap-2
|
|
129
|
+
// ${isFocused ? 'bg-gray-50 active:bg-gray-200' : ''}
|
|
130
|
+
// ${isSelected ? 'bg-gray-100 text-primary-dark' : ''}`,
|
|
131
|
+
// }}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div css={style} class="mt-input-before mb-input-after">
|
|
136
|
+
<ReactSelect
|
|
137
|
+
{...props}
|
|
138
|
+
_prefix={prefix}
|
|
139
|
+
_type={type||''}
|
|
140
|
+
_error={hasError}
|
|
141
|
+
key={value}
|
|
142
|
+
unstyled={true}
|
|
143
|
+
inputId={inputId || name}
|
|
144
|
+
filterOption={(option, searchText) => {
|
|
145
|
+
if (option.data?.fixed) return true
|
|
146
|
+
return filterFn(option, searchText)
|
|
147
|
+
}}
|
|
148
|
+
menuPlacement="auto"
|
|
149
|
+
minMenuHeight={250}
|
|
150
|
+
onChange={!onChange ? undefined : (o) => onChange({ target: { id: inputId || name, value: o?.value || o }})}
|
|
151
|
+
options={options}
|
|
152
|
+
value={value}
|
|
153
|
+
classNames={{
|
|
154
|
+
// Input container
|
|
155
|
+
control: (p) => getSelectStyle({ name: 'control', hasError, ...p }),
|
|
156
|
+
valueContainer: () => getSelectStyle({ name: 'valueContainer' }),
|
|
157
|
+
// Input container objects
|
|
158
|
+
input: () => getSelectStyle({ name: 'input', hasError }),
|
|
159
|
+
multiValue: () => getSelectStyle({ name: 'multiValue' }),
|
|
160
|
+
multiValueLabel: () => '',
|
|
161
|
+
multiValueRemove: () => getSelectStyle({ name: 'multiValueRemove' }),
|
|
162
|
+
placeholder: () => getSelectStyle({ name: 'placeholder' }),
|
|
163
|
+
singleValue: () => getSelectStyle({ name: 'singleValue', hasError }),
|
|
164
|
+
// Indicators
|
|
165
|
+
clearIndicator: () => getSelectStyle({ name: 'clearIndicator' }),
|
|
166
|
+
dropdownIndicator: () => getSelectStyle({ name: 'dropdownIndicator' }),
|
|
167
|
+
indicatorsContainer: () => getSelectStyle({ name: 'indicatorsContainer' }),
|
|
168
|
+
indicatorSeparator: () => getSelectStyle({ name: 'indicatorSeparator' }),
|
|
169
|
+
// Dropmenu
|
|
170
|
+
menu: () => getSelectStyle({ name: 'menu' }),
|
|
171
|
+
groupHeading: () => getSelectStyle({ name: 'groupHeading' }),
|
|
172
|
+
noOptionsMessage: () => getSelectStyle({ name: 'noOptionsMessage' }),
|
|
173
|
+
option: (p) => getSelectStyle({ name: 'option', ...p }),
|
|
174
|
+
}}
|
|
175
|
+
components={{
|
|
176
|
+
Control,
|
|
177
|
+
SingleValue,
|
|
178
|
+
Option,
|
|
179
|
+
DropdownIndicator, ClearIndicator, MultiValueRemove,
|
|
180
|
+
}}
|
|
181
|
+
styles={{
|
|
182
|
+
menu: (base) => ({
|
|
183
|
+
...base, minWidth: minMenuWidth
|
|
184
|
+
}),
|
|
185
|
+
// On mobile, the label will truncate automatically, so we want to
|
|
186
|
+
// override that behaviour.
|
|
187
|
+
multiValueLabel: (base) => ({
|
|
188
|
+
...base,
|
|
189
|
+
whiteSpace: "normal",
|
|
190
|
+
overflow: "visible",
|
|
191
|
+
}),
|
|
192
|
+
control: (base) => ({
|
|
193
|
+
...base,
|
|
194
|
+
outline: undefined,
|
|
195
|
+
transition: "none",
|
|
196
|
+
}),
|
|
197
|
+
}}
|
|
198
|
+
// menuIsOpen={true}
|
|
199
|
+
// isSearchable={false}
|
|
200
|
+
// isClearable={true}
|
|
201
|
+
// isMulti={true}
|
|
202
|
+
// isDisabled={true}
|
|
203
|
+
// maxMenuHeight={200}
|
|
204
|
+
/>
|
|
205
|
+
{hasError && <div class="mt-1.5 text-xs text-danger">{hasError.detail}</div>}
|
|
206
|
+
</div>
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function Control({ children, ...props }) {
|
|
211
|
+
// Add flag and prefix to the input (control)
|
|
212
|
+
// todo: check that the flag/prefix looks okay
|
|
213
|
+
const selectedOption = props.getValue()[0] || {}
|
|
214
|
+
return (
|
|
215
|
+
<components.Control {...props}>
|
|
216
|
+
{
|
|
217
|
+
(() => {
|
|
218
|
+
if (props.selectProps._prefix) {
|
|
219
|
+
return (
|
|
220
|
+
<>
|
|
221
|
+
<span class="relative right-[2px]">{props.selectProps._prefix}</span>
|
|
222
|
+
{children}
|
|
223
|
+
</>
|
|
224
|
+
)
|
|
225
|
+
} else if (props.selectProps._type == 'country') {
|
|
226
|
+
return (
|
|
227
|
+
<>
|
|
228
|
+
<Flag flag={selectedOption.flag} />
|
|
229
|
+
{children}
|
|
230
|
+
</>
|
|
231
|
+
)
|
|
232
|
+
} else {
|
|
233
|
+
return children
|
|
234
|
+
}
|
|
235
|
+
})()
|
|
236
|
+
}
|
|
237
|
+
</components.Control>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function SingleValue(props) {
|
|
242
|
+
const selectedOption = props.getValue()[0] || {}
|
|
243
|
+
return (
|
|
244
|
+
<components.SingleValue {...props}>
|
|
245
|
+
<>{selectedOption.labelControl || props.children}</>
|
|
246
|
+
</components.SingleValue>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function Option(props) {
|
|
251
|
+
// todo: check that the flag looks okay
|
|
252
|
+
return (
|
|
253
|
+
<components.Option className={props.data.className} {...props}>
|
|
254
|
+
{ props.selectProps._type == 'country' && <Flag flag={props.data.flag} /> }
|
|
255
|
+
<span class="flex-auto">{props.label}</span>
|
|
256
|
+
{props.isSelected && <CheckCircleIcon className="size-[22px] text-primary -my-1 -mx-1" />}
|
|
257
|
+
</components.Option>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const DropdownIndicator = (props) => {
|
|
262
|
+
return (
|
|
263
|
+
<components.DropdownIndicator {...props}>
|
|
264
|
+
{/* <img src="/assets/imgs/icons/v.svg" /> */}
|
|
265
|
+
<ChevronDownIcon className="size-6 -my-0.5 -mx-1" />
|
|
266
|
+
{/* <ChevronDownIcon /> */}
|
|
267
|
+
</components.DropdownIndicator>
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const ClearIndicator = (props) => {
|
|
272
|
+
return (
|
|
273
|
+
<components.ClearIndicator {...props}>
|
|
274
|
+
<XMarkIcon className="size-4 my-0.5" />
|
|
275
|
+
</components.ClearIndicator>
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const MultiValueRemove = (props) => {
|
|
280
|
+
return (
|
|
281
|
+
<components.MultiValueRemove {...props}>
|
|
282
|
+
<XMarkIcon className="size-4 p-[1px]" />
|
|
283
|
+
</components.MultiValueRemove>
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
function Flag({ flag }) {
|
|
289
|
+
if (!flag) return null
|
|
290
|
+
return (
|
|
291
|
+
<span class="flag" style={{ backgroundImage: `url(/assets/imgs/flags/${flag}.svg)` }} />
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const style = () => css`
|
|
296
|
+
/*
|
|
297
|
+
todo: add these as tailwind classes
|
|
298
|
+
|
|
299
|
+
&.rs-medium {
|
|
300
|
+
.rs__control {
|
|
301
|
+
padding: 9px 13px;
|
|
302
|
+
font-size: 13px;
|
|
303
|
+
font-weight: 400;
|
|
304
|
+
min-height: 0;
|
|
305
|
+
}
|
|
306
|
+
.rs__menu {
|
|
307
|
+
.rs__option {
|
|
308
|
+
font-size: 0.85rem;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
&.rs-small {
|
|
313
|
+
.rs__control {
|
|
314
|
+
padding: 5px 13px;
|
|
315
|
+
font-size: 12.5px;
|
|
316
|
+
font-weight: 400;
|
|
317
|
+
min-height: 0;
|
|
318
|
+
}
|
|
319
|
+
.rs__menu {
|
|
320
|
+
.rs__option {
|
|
321
|
+
font-size: 0.8rem;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
} */
|
|
325
|
+
|
|
326
|
+
/*
|
|
327
|
+
todo: not yet accounted for (maybe remove)
|
|
328
|
+
|
|
329
|
+
.bb:not(:last-child) {
|
|
330
|
+
// option border
|
|
331
|
+
position: relative;
|
|
332
|
+
margin-bottom: 6px;
|
|
333
|
+
&:after {
|
|
334
|
+
content: "";
|
|
335
|
+
position: absolute;
|
|
336
|
+
bottom: -4px;
|
|
337
|
+
left: 0;
|
|
338
|
+
height: 2px;
|
|
339
|
+
width: 100%;
|
|
340
|
+
background-color: $border-color;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
.bt:not(:first-child) {
|
|
344
|
+
// option border
|
|
345
|
+
position: relative;
|
|
346
|
+
margin-top: 6px;
|
|
347
|
+
&:after {
|
|
348
|
+
content: "";
|
|
349
|
+
position: absolute;
|
|
350
|
+
top: -4px;
|
|
351
|
+
left: 0;
|
|
352
|
+
height: 2px;
|
|
353
|
+
width: 100%;
|
|
354
|
+
background-color: $border-color;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
.flag {
|
|
358
|
+
// https://github.com/lipis/flag-icons
|
|
359
|
+
flex-shrink: 0;
|
|
360
|
+
margin-right: 10px;
|
|
361
|
+
width: 21px;
|
|
362
|
+
height: 14px;
|
|
363
|
+
background-size: cover;
|
|
364
|
+
background-repeat: no-repeat;
|
|
365
|
+
background-position: center;
|
|
366
|
+
border-radius: 3px;
|
|
367
|
+
overflow: hidden;
|
|
368
|
+
}*/
|
|
369
|
+
`
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export function Toggle({ name, id, subtext, text, type = 'checkbox', ...props }) {
|
|
2
|
+
if (!name) throw new Error('Toggle requires a `name` prop')
|
|
3
|
+
id = id || name
|
|
4
|
+
// https://tailwindui.com/components/application-ui/forms/checkboxes#component-744ed4fa65ba36b925701eb4da5c6e31
|
|
5
|
+
return (
|
|
6
|
+
<div className={`mt-input-before mb-input-after flex gap-3 ${props.className || ''}`}>
|
|
7
|
+
<div className="flex h-6 shrink-0 items-center">
|
|
8
|
+
<div className="group grid size-4 grid-cols-1">
|
|
9
|
+
<input
|
|
10
|
+
{...props}
|
|
11
|
+
id={id}
|
|
12
|
+
name={name}
|
|
13
|
+
type={type}
|
|
14
|
+
className="col-start-1 row-start-1 appearance-none rounded border border-gray-300 bg-white checked:border-indigo-600 checked:bg-indigo-600 indeterminate:border-indigo-600 indeterminate:bg-indigo-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:border-gray-300 disabled:bg-gray-100 disabled:checked:bg-gray-100 forced-colors:appearance-auto"
|
|
15
|
+
/>
|
|
16
|
+
<svg
|
|
17
|
+
fill="none"
|
|
18
|
+
viewBox="0 0 14 14"
|
|
19
|
+
className="pointer-events-none col-start-1 row-start-1 size-3.5 self-center justify-self-center stroke-white group-has-[:disabled]:stroke-gray-950/25"
|
|
20
|
+
>
|
|
21
|
+
<path
|
|
22
|
+
d="M3 8L6 11L11 3.5"
|
|
23
|
+
strokeWidth={2}
|
|
24
|
+
strokeLinecap="round"
|
|
25
|
+
strokeLinejoin="round"
|
|
26
|
+
className="opacity-0 group-has-[:checked]:opacity-100"
|
|
27
|
+
/>
|
|
28
|
+
<path
|
|
29
|
+
d="M3 7H11"
|
|
30
|
+
strokeWidth={2}
|
|
31
|
+
strokeLinecap="round"
|
|
32
|
+
strokeLinejoin="round"
|
|
33
|
+
className="opacity-0 group-has-[:indeterminate]:opacity-100"
|
|
34
|
+
/>
|
|
35
|
+
</svg>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
{text && <div className="text-sm/6">
|
|
39
|
+
<label for={id}>
|
|
40
|
+
<span className="font-medium text-gray-900">{text}</span>
|
|
41
|
+
<span className="ml-2 text-gray-500">{subtext}</span>
|
|
42
|
+
</label>
|
|
43
|
+
</div>}
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function IsFirstRender(delay) {
|
|
2
|
+
/*
|
|
3
|
+
* Checks if the current render of a react component is the first
|
|
4
|
+
* E.g. const isFirst = isFirstRender()
|
|
5
|
+
* @param {boolean} delay
|
|
6
|
+
* @link https://stackoverflow.com/a/56267719/1900648
|
|
7
|
+
* @return boolean
|
|
8
|
+
*/
|
|
9
|
+
const isMountRef = useRef(true)
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (delay) setTimeout(() => isMountRef.current = false, delay)
|
|
12
|
+
else isMountRef.current = false
|
|
13
|
+
}, [])
|
|
14
|
+
return isMountRef.current
|
|
15
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { css } from 'twin.macro'
|
|
2
|
+
import { Outlet } from 'react-router-dom'
|
|
3
|
+
import { Message } from '../element/message.jsx'
|
|
4
|
+
import { Sidebar } from '../element/sidebar.jsx'
|
|
5
|
+
|
|
6
|
+
// Dashboard, app screens (only the <Outlet/> receives `params` and `location`)
|
|
7
|
+
export function Layout1({ Logo }) {
|
|
8
|
+
const [store] = sharedStore.useTracked()
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div css={style} class="bg-[#F3F3F3]">
|
|
12
|
+
<Message />
|
|
13
|
+
<div class="flex-1">
|
|
14
|
+
<div class="wrapper lg:flex min-h-[100%] w-[100%] bg-[#FDFDFD] shadow-[0_0_40px_0_rgb(237_237_237)]">
|
|
15
|
+
<Sidebar Logo={Logo} />
|
|
16
|
+
<div class="py-10 px-14 flex-1" key={store?.company?._id}>
|
|
17
|
+
<Outlet />
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const style = () => css`
|
|
26
|
+
.wrapper {
|
|
27
|
+
position: relative;
|
|
28
|
+
max-width: 1800px;
|
|
29
|
+
margin: 0 auto;
|
|
30
|
+
}
|
|
31
|
+
`
|
|
32
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { css } from 'twin.macro'
|
|
2
|
+
import { Outlet } from 'react-router-dom'
|
|
3
|
+
import { Message } from '../element/message.jsx'
|
|
4
|
+
|
|
5
|
+
// Signin, reset password, etc
|
|
6
|
+
export function Layout2({ Logo }) {
|
|
7
|
+
return (
|
|
8
|
+
<div css={style} class="bg-[#F3F3F3]">
|
|
9
|
+
<Message />
|
|
10
|
+
<div class="wrapper bg-[#FDFDFD] shadow-[0_0_40px_0_rgb(237_237_237)] flex flex-col min-h-full w-full">
|
|
11
|
+
<div class="flex-1 w-full wrapper-2 px-5 py-10">
|
|
12
|
+
<div class="border-b mb-6">
|
|
13
|
+
<Link to="/signin" class="logo relative block -ml-1 -mt-1 p-1">
|
|
14
|
+
<Logo alt="Nitro" width="60" />
|
|
15
|
+
</Link>
|
|
16
|
+
</div>
|
|
17
|
+
<Outlet />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="wrapper-2 w-full px-5 pb-4 flex items-center w-full text-sm text-[#1F1F1F]">
|
|
21
|
+
<ul class="flex-1 flex gap-4 list-style-none">
|
|
22
|
+
<li><Link class="underline1" to="/">Home</Link></li>
|
|
23
|
+
<li><Link class="underline1" to="/signin">About</Link></li>
|
|
24
|
+
<li><Link class="underline1" to="/signin">Support</Link></li>
|
|
25
|
+
</ul>
|
|
26
|
+
<div>
|
|
27
|
+
2025 © Nitro
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const style = () => css`
|
|
37
|
+
.wrapper {
|
|
38
|
+
position: relative;
|
|
39
|
+
max-width: 1800px;
|
|
40
|
+
margin: 0 auto;
|
|
41
|
+
}
|
|
42
|
+
.wrapper-2 {
|
|
43
|
+
max-width: 700px;
|
|
44
|
+
margin: 0 auto;
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
}
|
|
47
|
+
`
|