back-testing-react 1.0.4 → 1.0.6

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 (60) hide show
  1. package/dist/cjs/index.js +17 -17
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/esm/index.js +17 -17
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/index.d.ts +35 -10
  6. package/package.json +5 -1
  7. package/src/components/back-testing-cat-legend/back-testing-cat-legend.css +1 -1
  8. package/src/components/back-testing-cat-legend/back-testing-cat-legend.tsx +7 -7
  9. package/src/components/back-testing-map/back-testing-map.css +1 -1
  10. package/src/components/back-testing-map/back-testing-map.service.ts +21 -5
  11. package/src/components/back-testing-map/back-testing-map.tsx +51 -18
  12. package/src/components/back-testing-map/back-testing-map.types.ts +74 -11
  13. package/src/components/back-testing-payout-info/back-testing-payout-info.css +112 -0
  14. package/src/components/back-testing-payout-info/back-testing-payout-info.tsx +371 -0
  15. package/src/components/back-testing-payout-info/back-testing-payout-info.types.ts +9 -0
  16. package/src/components/back-testing-stepper/back-testing-stepper.css +30 -0
  17. package/src/components/back-testing-stepper/back-testing-stepper.tsx +322 -0
  18. package/src/components/back-testing-stepper/back-testing-stepper.types.ts +41 -0
  19. package/src/components/back-testing-stepper/inputs/distance-input/distance-input.css +57 -0
  20. package/src/components/back-testing-stepper/inputs/distance-input/distance-input.stories.tsx +20 -0
  21. package/src/components/back-testing-stepper/inputs/distance-input/distance-input.tsx +93 -0
  22. package/src/components/back-testing-stepper/inputs/distance-input/distance-input.types.ts +41 -0
  23. package/src/components/back-testing-stepper/inputs/file-input/file-input.css +26 -0
  24. package/src/components/back-testing-stepper/inputs/file-input/file-input.stories.tsx +20 -0
  25. package/src/components/back-testing-stepper/inputs/file-input/file-input.tsx +43 -0
  26. package/src/components/back-testing-stepper/inputs/file-input/file-input.types.ts +10 -0
  27. package/src/components/back-testing-stepper/inputs/number-input/number-input.css +48 -0
  28. package/src/components/back-testing-stepper/inputs/number-input/number-input.stories.tsx +20 -0
  29. package/src/components/back-testing-stepper/inputs/number-input/number-input.tsx +55 -0
  30. package/src/components/back-testing-stepper/inputs/number-input/number-input.types.ts +12 -0
  31. package/src/components/back-testing-stepper/inputs/select-input/select-input.css +13 -0
  32. package/src/components/back-testing-stepper/inputs/select-input/select-input.stories.tsx +27 -0
  33. package/src/components/back-testing-stepper/inputs/select-input/select-input.tsx +63 -0
  34. package/src/components/back-testing-stepper/inputs/select-input/select-input.types.ts +8 -0
  35. package/src/components/back-testing-stepper/inputs/text-input/text-input.css +35 -0
  36. package/src/components/back-testing-stepper/inputs/text-input/text-input.stories.tsx +18 -0
  37. package/src/components/back-testing-stepper/inputs/text-input/text-input.tsx +32 -0
  38. package/src/components/back-testing-stepper/inputs/text-input/text-input.types.ts +6 -0
  39. package/src/components/back-testing-stepper/inputs/worldmap-search/worldmap-search.css +16 -0
  40. package/src/components/back-testing-stepper/inputs/worldmap-search/worldmap-search.stories.tsx +20 -0
  41. package/src/components/back-testing-stepper/inputs/worldmap-search/worldmap-search.tsx +151 -0
  42. package/src/components/back-testing-stepper/inputs/worldmap-search/worldmap-search.types.ts +11 -0
  43. package/src/components/back-testing-stepper/steps/input-anemometer/input-anemometer.css +0 -0
  44. package/src/components/back-testing-stepper/steps/input-anemometer/input-anemometer.tsx +188 -0
  45. package/src/components/back-testing-stepper/steps/input-anemometer/input-anemometer.types.ts +16 -0
  46. package/src/components/back-testing-stepper/steps/input-ciac/input-ciac.scss +0 -0
  47. package/src/components/back-testing-stepper/steps/input-ciac/input-ciac.tsx +225 -0
  48. package/src/components/back-testing-stepper/steps/input-ciac/input-ciac.types.ts +17 -0
  49. package/src/components/back-testing-stepper/steps/input-location/input-location.css +0 -0
  50. package/src/components/back-testing-stepper/steps/input-location/input-location.tsx +135 -0
  51. package/src/components/back-testing-stepper/steps/input-location/input-location.types.ts +16 -0
  52. package/src/components/back-testing-stepper/steps/input-proxy/input-proxy.css +0 -0
  53. package/src/components/back-testing-stepper/steps/input-proxy/input-proxy.tsx +173 -0
  54. package/src/components/back-testing-stepper/steps/input-proxy/input-proxy.types.ts +16 -0
  55. package/src/components/back-testing-stepper/steps/step.types.ts +58 -0
  56. package/src/components/back-testing-storm-legend/back-testing-storm-legend.stories.tsx +56 -33
  57. package/src/components/back-testing-wizard/back-testing-wizard.css +36 -0
  58. package/src/components/back-testing-wizard/back-testing-wizard.stories.tsx +734 -0
  59. package/src/components/back-testing-wizard/back-testing-wizard.tsx +102 -0
  60. package/src/components/back-testing-wizard/back-testing-wizard.types.ts +12 -0
@@ -0,0 +1,322 @@
1
+ import { BackTestingStepperProps } from "./back-testing-stepper.types"
2
+ import "./back-testing-stepper.css"
3
+ import { PayoutWizardStep, PayoutWizardStepType, RadioChoice, WizardStep } from "./steps/step.types";
4
+ import mapboxgl, { GeoJSONSource } from 'mapbox-gl';
5
+ import React, { useEffect, useRef } from "react";
6
+ import { AnemometerRequest, CIAC_LAYER, CIACRequest, DEFAULT_MAP_COORDINATE, DEFAULT_MAP_ZOOM, generateAnemometerRequest, generateCIACRequest, generateInitialPayoutRequest, generateProxyRequest, PayoutRequest, ProxyRequest, TROPICAL_STORM_LAYER, TROPICAL_STORM_TRACK_LAYER } from "../back-testing-map/back-testing-map.types";
7
+ import InputLocationStep from "./steps/input-location/input-location";
8
+ import InputProxyStep from "./steps/input-proxy/input-proxy";
9
+ import InputAnemometerStep from "./steps/input-anemometer/input-anemometer";
10
+ import InputCIACStep from "./steps/input-ciac/input-ciac";
11
+ import Stepper from '@mui/material/Stepper';
12
+ import Step from '@mui/material/Step';
13
+ import StepLabel from '@mui/material/StepLabel';
14
+ import StepContent from '@mui/material/StepContent';
15
+ import { Box, Button, Switch, Typography } from "@mui/material";
16
+ import BackTestingPayoutInfo from "../back-testing-payout-info/back-testing-payout-info";
17
+
18
+ const BackTestingStepper = (props: BackTestingStepperProps) => {
19
+
20
+ const label = { inputProps: { 'aria-label': 'Step Switch' } };
21
+ const [payoutRequest, setPayoutRequest] = React.useState<PayoutRequest>(props.payoutRequest);
22
+ const [proxy, setProxy] = React.useState<ProxyRequest>(payoutRequest.proxy);
23
+ const [anemometer, setAnemometer] = React.useState<AnemometerRequest>(payoutRequest.anemometer);
24
+ const [ciac, setCIAC] = React.useState<CIACRequest>(payoutRequest.ciac);
25
+
26
+ const handleStepToggle = (step:PayoutWizardStep, val:boolean) => {
27
+ if(step.step == PayoutWizardStepType.INPUT_PROXY_DETAILS){
28
+ step.enabled[1](val);
29
+ let proxy : ProxyRequest = generateProxyRequest(
30
+ [payoutRequest.proxy.longitude,payoutRequest.proxy.latitude],
31
+ payoutRequest.proxy.radius,
32
+ payoutRequest.proxy.payouts,
33
+ val
34
+ );
35
+ setProxy(proxy);
36
+ } else if(step.step == PayoutWizardStepType.INPUT_ANEMOMETER_DETAILS){
37
+ step.enabled[1](val);
38
+ let anemometer : AnemometerRequest = generateAnemometerRequest(
39
+ [payoutRequest.anemometer.longitude,payoutRequest.anemometer.latitude],
40
+ payoutRequest.anemometer.code,
41
+ payoutRequest.anemometer.payouts,
42
+ val
43
+ );
44
+ setAnemometer(anemometer);
45
+ } else if(step.step == PayoutWizardStepType.INPUT_CIAC_DETAILS){
46
+ step.enabled[1](val);
47
+ let ciac : CIACRequest = generateCIACRequest(
48
+ [payoutRequest.longitude,payoutRequest.latitude],
49
+ payoutRequest.ciac.shapes[0].radius,
50
+ payoutRequest.ciac.includeNotNamedStorms,
51
+ payoutRequest.ciac.payouts,
52
+ val
53
+ );
54
+ setCIAC(ciac);
55
+ }
56
+ }
57
+
58
+ // simplified step wizard
59
+ const simplifiedSteps : PayoutWizardStep[] = [
60
+ {
61
+ step: PayoutWizardStepType.SIMPLIFIED_INPUT_LOCATION,
62
+ label: 'Input Risk Details',
63
+ description: `Input your risk location address, the corresponding limit of insurance, and the desired CIAC radius.`,
64
+ componentRef: useRef<WizardStep>(null),
65
+ istoggable:false,
66
+ enabled:React.useState(true)
67
+ }
68
+ ];
69
+
70
+ // extended step wizard
71
+ const extendedSteps : PayoutWizardStep[] = [
72
+ {
73
+ step: PayoutWizardStepType.INPUT_LOCATION,
74
+ label: 'Risk Location',
75
+ description: `Input your risk location address and corresponding limit of insurance.`,
76
+ componentRef: useRef<WizardStep>(null),
77
+ istoggable:false,
78
+ enabled:React.useState(true)
79
+ },
80
+ {
81
+ step: PayoutWizardStepType.INPUT_PROXY_DETAILS,
82
+ label: 'Proxy Details',
83
+ description: `Choose between a preconfigured payout table or upload a custom table.`,
84
+ componentRef: useRef<WizardStep>(null),
85
+ istoggable:true,
86
+ enabled:React.useState(payoutRequest.proxy.enabled)
87
+ },
88
+ {
89
+ step: PayoutWizardStepType.INPUT_ANEMOMETER_DETAILS,
90
+ label: 'Anemometer Details',
91
+ description: `Input the anemometer station ID and choose between a preconfigured payout table or upload a custom table.`,
92
+ componentRef: useRef<WizardStep>(null),
93
+ istoggable:true,
94
+ enabled:React.useState(payoutRequest.anemometer.enabled)
95
+ },
96
+ {
97
+ step: PayoutWizardStepType.INPUT_CIAC_DETAILS,
98
+ label: 'CIAC Details',
99
+ description: `Choose between a preconfigured payout table or upload a custom table.`,
100
+ componentRef: useRef<WizardStep>(null),
101
+ istoggable:true,
102
+ enabled:React.useState(payoutRequest.ciac.enabled)
103
+ }
104
+ ];
105
+
106
+ const steps = extendedSteps
107
+ const [activeStep, setActiveStep] = React.useState(0);
108
+ const [locationMarker, setLocationMarker] = React.useState<mapboxgl.Marker>(new mapboxgl.Marker());
109
+ const [wizardStep, setWizardStep] = React.useState<PayoutWizardStepType>(props.currentStep);
110
+ const [locationAddress, setLocationAddress] = React.useState<string>("");
111
+ const [proxyChoice, setProxyChoice] = React.useState<RadioChoice>(props.proxyChoice);
112
+ const [anemometerChoice, setAnemometerChoice] = React.useState<RadioChoice>(props.proxyChoice);
113
+ const [ciacChoice, setCIACChoice] = React.useState<RadioChoice>(props.proxyChoice);
114
+ const [selectedProxyPayoutOption, setProxyPayoutOption] = React.useState<number>(props.selectedProxyPayoutOptionIndex);
115
+ const [selectedAnemometerPayoutOption, setAnemometerPayoutOption] = React.useState<number>(props.selectedAnemometerPayoutOptionIndex);
116
+ const [selectedCIACPayoutOption, setCIACPayoutOption] = React.useState<number>(props.selectedCIACPayoutOptionIndex);
117
+
118
+ const handleProxyPayoutOptionChange = (e:number) => {
119
+ setProxyPayoutOption(e)
120
+ props.onProxyPayoutOptionChange(e);
121
+ }
122
+
123
+ const handleAnemometerPayoutOptionChange = (e:number) => {
124
+ setAnemometerPayoutOption(e)
125
+ props.onAnemometerPayoutOptionChange(e);
126
+ }
127
+
128
+ const handleCIACPayoutOptionChange = (e:number) => {
129
+ setCIACPayoutOption(e)
130
+ props.onCIACPayoutOptionChange(e);
131
+ }
132
+
133
+ useEffect(() => {
134
+ let request : PayoutRequest = {
135
+ includeZeroPayouts: payoutRequest.includeZeroPayouts,
136
+ limit: payoutRequest.limit,
137
+ latitude: payoutRequest.latitude,
138
+ longitude: payoutRequest.longitude,
139
+ proxy: proxy,
140
+ anemometer: anemometer,
141
+ ciac: ciac
142
+ }
143
+ setPayoutRequest(request);
144
+ props.onPayoutRequestChange(request);
145
+ }, [ciac,proxy,anemometer])
146
+
147
+ const handleNext = (step:{ step: PayoutWizardStepType, label: string, description: string, componentRef: React.RefObject<WizardStep>}) => {
148
+ step.componentRef.current?.handleNext();
149
+ }
150
+
151
+ const handleBack = (step:{ step: PayoutWizardStepType, label: string, description: string, componentRef: React.RefObject<WizardStep>}) => {
152
+ step.componentRef.current?.handleBack();
153
+ }
154
+
155
+ const handleReset = (step: PayoutWizardStepType) => {
156
+ setLocationAddress("");
157
+ setWizardStep(step);
158
+ if(props.onStormPayoutsChanged != undefined){
159
+ props.onStormPayoutsChanged([])
160
+ }
161
+ props.onCurrentStepChange(step);
162
+ props.onProxyChoiceChange(RadioChoice.DEFAULT);
163
+ props.onAnemometerChoiceChange(RadioChoice.DEFAULT);
164
+ props.onCIACChoiceChange(RadioChoice.DEFAULT);
165
+ props.onProxyPayoutOptionChange(0);
166
+ props.onAnemometerPayoutOptionChange(0);
167
+ props.onCIACPayoutOptionChange(0);
168
+ locationMarker.remove()
169
+ setActiveStep(0);
170
+ let resetPayoutRequest = generateInitialPayoutRequest();
171
+ setPayoutRequest(resetPayoutRequest);
172
+ props.onPayoutRequestChange(resetPayoutRequest);
173
+ const catSource = (props.mapInstanceRef.current as mapboxgl.Map).getSource(CIAC_LAYER)
174
+ if (catSource){
175
+ (catSource as GeoJSONSource).setData({type: 'FeatureCollection', features: []})
176
+ }
177
+ const circleSource = (props.mapInstanceRef.current as mapboxgl.Map).getSource(TROPICAL_STORM_LAYER)
178
+ if (circleSource){
179
+ (circleSource as GeoJSONSource).setData({type: 'FeatureCollection', features: []})
180
+ }
181
+ const lineSource = (props.mapInstanceRef.current as mapboxgl.Map).getSource(TROPICAL_STORM_TRACK_LAYER)
182
+ if (lineSource){
183
+ (lineSource as GeoJSONSource).setData({type: 'FeatureCollection', features: []})
184
+ }
185
+ (props.mapInstanceRef.current as mapboxgl.Map).flyTo({
186
+ center: DEFAULT_MAP_COORDINATE,
187
+ essential: true,
188
+ zoom: DEFAULT_MAP_ZOOM})
189
+ };
190
+
191
+ function renderStepInputs(step:{ step: PayoutWizardStepType, label: string, description: string, componentRef: React.MutableRefObject<any>}) {
192
+ switch(step.step) {
193
+ case PayoutWizardStepType.INPUT_LOCATION:
194
+ return <>
195
+ <InputLocationStep
196
+ ref={step.componentRef}
197
+ accessToken={props.mapAccessToken}
198
+ mapInstanceRef={props.mapInstanceRef}
199
+ marker={locationMarker}
200
+ activeStep={activeStep}
201
+ payoutRequest={payoutRequest}
202
+ address={locationAddress}
203
+ onActiveStepChange={setActiveStep}
204
+ onStepChange={setWizardStep}
205
+ onLocationAddressChange={setLocationAddress}
206
+ onPayoutRequestChange={setPayoutRequest}/>
207
+ </>;
208
+ case PayoutWizardStepType.INPUT_PROXY_DETAILS:
209
+ return <>
210
+ <InputProxyStep
211
+ ref={step.componentRef}
212
+ mapInstanceRef={props.mapInstanceRef}
213
+ activeStep={activeStep}
214
+ proxyChoice={proxyChoice}
215
+ request={payoutRequest}
216
+ onActiveStepChange={setActiveStep}
217
+ onStepChange={setWizardStep}
218
+ payoutOptions={props.proxyPayoutOptions}
219
+ selectedPayoutOptionIndex={selectedProxyPayoutOption}
220
+ onPayoutOptionChange={handleProxyPayoutOptionChange}
221
+ onProxyChoiceChange={setProxyChoice}
222
+ onProxyRequestChange={setProxy}
223
+ />
224
+ </>;
225
+ case PayoutWizardStepType.INPUT_ANEMOMETER_DETAILS:
226
+ return <>
227
+ <InputAnemometerStep
228
+ ref={step.componentRef}
229
+ mapInstanceRef={props.mapInstanceRef}
230
+ activeStep={activeStep}
231
+ request={payoutRequest}
232
+ anemometerChoice={anemometerChoice}
233
+ selectedPayoutOptionIndex={selectedAnemometerPayoutOption}
234
+ payoutOptions={props.anemometerPayoutOptions}
235
+ onActiveStepChange={setActiveStep}
236
+ onStepChange={setWizardStep}
237
+ onAnemometerChoiceChange={setAnemometerChoice}
238
+ onPayoutOptionChange={handleAnemometerPayoutOptionChange}
239
+ onAnemometerRequestChange={setAnemometer}/>
240
+ </>;
241
+ case PayoutWizardStepType.INPUT_CIAC_DETAILS:
242
+ return <>
243
+ <InputCIACStep
244
+ ref={step.componentRef}
245
+ mapInstanceRef={props.mapInstanceRef}
246
+ activeStep={activeStep}
247
+ request={payoutRequest}
248
+ ciacChoice={ciacChoice}
249
+ selectedPayoutOptionIndex={selectedCIACPayoutOption}
250
+ payoutOptions={props.ciacPayoutOptions}
251
+ onActiveStepChange={setActiveStep}
252
+ onStepChange={setWizardStep}
253
+ onCIACChoiceChange={setCIACChoice}
254
+ onPayoutOptionChange={handleCIACPayoutOptionChange}
255
+ onCalculatePayoutEnabledChanged={props.onCalculatePayoutEnabledChanged}
256
+ onCIACRequestChange={setCIAC}/>
257
+ </>;
258
+ case PayoutWizardStepType.SIMPLIFIED_INPUT_LOCATION:
259
+ return <></>;
260
+ default:
261
+ return <></>;
262
+ }
263
+ }
264
+
265
+ return(
266
+ <div className="stepper-container">
267
+ <Box sx={{ maxWidth: 400 }}>
268
+ {wizardStep !== PayoutWizardStepType.DISPLAY_PAYOUTS && <Stepper activeStep={activeStep} orientation="vertical">
269
+ {steps.map((step, index) => (
270
+ <Step key={step.label}>
271
+ <StepLabel className={index === activeStep ? 'active' : ''} icon={steps.length == 1 ? <></> : undefined} sx={steps.length == 1 ? {pl:'5px'} : {}}>
272
+ {step.label}
273
+ {step.istoggable && <Switch checked={step.enabled[0]} onChange={(e) => {handleStepToggle(step,e.target.checked)}} inputProps={{ 'aria-label': 'controlled' }} />}
274
+ </StepLabel>
275
+ <StepContent sx={steps.length == 1 ? {ml:0,pl:'15px',pr:'20px'} : {ml:'20px',pl:'20px', pr:'20px', mr:'10px' }}>
276
+ <Typography>{step.description}</Typography>
277
+ <Box sx={{ mb: 2 }}>
278
+ <div>
279
+ {renderStepInputs(step)}
280
+ <Button
281
+ variant="contained"
282
+ onClick={() => handleNext(step)}
283
+ sx={{ mt: 1, mr: 1 }}
284
+ >
285
+ {index === steps.length - 1 ? 'Calculate Payouts' : 'Continue'}
286
+ </Button>
287
+ {steps.length > 1 && <Button
288
+ disabled={index === 0}
289
+ className='back'
290
+ onClick={() => handleBack(step)}
291
+ sx={{ mt: 1, mr: 1 }}
292
+ >
293
+ Back
294
+ </Button>}
295
+ </div>
296
+ </Box>
297
+ </StepContent>
298
+ </Step>
299
+ ))}
300
+ </Stepper>}
301
+ {wizardStep === PayoutWizardStepType.DISPLAY_PAYOUTS && steps.length > 1 && (
302
+ <BackTestingPayoutInfo
303
+ mapInstanceRef={props.mapInstanceRef}
304
+ stormPayouts={props.stormPayouts}
305
+ status={props.appStatus}
306
+ onRestart={() => handleReset(PayoutWizardStepType.INPUT_LOCATION)}
307
+ onBack={() => {setWizardStep(PayoutWizardStepType.INPUT_CIAC_DETAILS)}}/>
308
+ )}
309
+ {wizardStep === PayoutWizardStepType.DISPLAY_PAYOUTS && steps.length == 1 &&(
310
+ <BackTestingPayoutInfo
311
+ mapInstanceRef={props.mapInstanceRef}
312
+ stormPayouts={props.stormPayouts}
313
+ status={props.appStatus}
314
+ onRestart={() => handleReset(PayoutWizardStepType.SIMPLIFIED_INPUT_LOCATION)}
315
+ onBack={() => {setWizardStep(PayoutWizardStepType.SIMPLIFIED_INPUT_LOCATION)}}/>
316
+ )}
317
+ </Box>
318
+ </div>
319
+ );
320
+ }
321
+
322
+ export default BackTestingStepper
@@ -0,0 +1,41 @@
1
+ import { StormPayout } from "../back-testing-map/back-testing-map.service";
2
+ import { Payout, PayoutRequest } from "../back-testing-map/back-testing-map.types"
3
+ import { PayoutWizardStepType, RadioChoice, WizardStep } from "./steps/step.types"
4
+
5
+ export interface BackTestingStepperProps {
6
+ apiKey : string,
7
+ mapAccessToken : string,
8
+ mapInstanceRef : React.MutableRefObject<any>,
9
+ currentStep: PayoutWizardStepType,
10
+ onCurrentStepChange: (e: PayoutWizardStepType) => void,
11
+ payoutRequest: PayoutRequest,
12
+ onPayoutRequestChange: (e:PayoutRequest) => void,
13
+ proxyChoice: RadioChoice,
14
+ onProxyChoiceChange: (e:RadioChoice) => void;
15
+ anemometerChoice: RadioChoice,
16
+ onAnemometerChoiceChange: (e:RadioChoice) => void;
17
+ ciacChoice: RadioChoice,
18
+ onCIACChoiceChange: (e:RadioChoice) => void;
19
+ onProxyPayoutOptionChange: (e: number) => void;
20
+ selectedProxyPayoutOptionIndex:number;
21
+ proxyPayoutOptions: {key:string,value: Payout[]}[],
22
+ onAnemometerPayoutOptionChange: (e: number) => void;
23
+ selectedAnemometerPayoutOptionIndex:number;
24
+ anemometerPayoutOptions: {key:string,value: Payout[]}[],
25
+ onCIACPayoutOptionChange: (e: number) => void;
26
+ selectedCIACPayoutOptionIndex:number;
27
+ ciacPayoutOptions: {key:string,value: Payout[]}[],
28
+ stormPayouts: StormPayout[],
29
+ onStormPayoutsChanged?: (e:StormPayout[]) => void,
30
+ onCalculatePayoutEnabledChanged: (e:boolean) => void,
31
+ appStatus: 'loading'|'idle'
32
+ }
33
+
34
+ export type PayoutWizardStep = {
35
+ step: PayoutWizardStepType
36
+ label: string
37
+ description: string
38
+ componentRef: React.RefObject<WizardStep>
39
+ istoggable:boolean,
40
+ enabled: [boolean, React.Dispatch<React.SetStateAction<boolean>>]
41
+ }
@@ -0,0 +1,57 @@
1
+ .distance-input-container{
2
+ padding: 0;
3
+ font-family: system-ui, sans-serif;
4
+ }
5
+
6
+ .distance-input-container .label{
7
+ font-weight: 700;
8
+ padding-bottom: 5px;
9
+ }
10
+
11
+ .distance-input-container .flex-container{
12
+ border: 0.5px solid lightgray;
13
+ display:flex;
14
+ }
15
+
16
+ .distance-input-container .flex-container .prefix{
17
+ background-color: lightgray;
18
+ padding: 8px 15px;
19
+ }
20
+
21
+ .distance-input-container .flex-container .distance-options #distance{
22
+ height: 100%;
23
+ outline: none;
24
+ box-shadow: none;
25
+ border: 0;
26
+ background-color: lightgrey;
27
+ padding-left: 10px;
28
+ }
29
+
30
+ .distance-input-container .flex-container.error{
31
+ border: 0.5px solid rgb(211,47,47);
32
+ }
33
+
34
+ .distance-input-container .flex-container.error:focus-within{
35
+ border: 0.5px solid rgb(211,47,47) !important;
36
+ box-shadow: inset 1px 1px rgb(211,47,47), inset -1px -1px rgb(211,47,47);
37
+ }
38
+
39
+ .distance-input-container .flex-container.error:focus-within .prefix{
40
+ box-shadow: inset 1px 1px rgb(211,47,47), inset 0px -1px rgb(211,47,47);
41
+ }
42
+
43
+ .distance-input-container .flex-container:hover:not(.error:focus-within){
44
+ border: 0.5px solid black;
45
+ }
46
+
47
+ .distance-input-container input{
48
+ width: 100%;
49
+ padding: 8px 10px;
50
+ font-size: 1rem;
51
+ border: 0;
52
+ background-color: rgba(255,255,255,0);
53
+ }
54
+
55
+ .distance-input-container input:focus-visible{
56
+ outline: 0;
57
+ }
@@ -0,0 +1,20 @@
1
+ import { StoryFn, Meta } from "@storybook/react";
2
+ import DistanceInput from "./distance-input";
3
+
4
+ export default {
5
+ title: "ReactComponentLibrary/Inputs",
6
+ component: DistanceInput,
7
+ } as Meta<typeof DistanceInput>;
8
+
9
+ const Template: StoryFn<typeof DistanceInput> = (args) => <DistanceInput {...args} />;
10
+
11
+ export const DistanceInputTest = Template.bind({});
12
+ DistanceInputTest.args = {
13
+ label:"Circle radius:",
14
+ value:15,
15
+ distanceOptions:[{name:'mi',symbol:'mi'},{name:'km',symbol:'km'}],
16
+ error:false,
17
+ errorMessage:"Circle radius must be a number greater than zero",
18
+ step:0.1,
19
+ min:0
20
+ };
@@ -0,0 +1,93 @@
1
+ "use client"
2
+
3
+ import React from "react";
4
+ import "./distance-input.css";
5
+ import { DistanceInputProps, DistanceUnit, kilometersToMiles, metersToMiles, milesToKilometers, milesToMeters } from "./distance-input.types";
6
+
7
+ function DistanceInput(props: DistanceInputProps){
8
+
9
+ const [milesValue, setMilesValue] = React.useState(props.value);
10
+ const [inputValue, setInputValue] = React.useState(props.value);
11
+ const [currentDistanceUnit, setCurrentDistanceUnit] = React.useState(props.unit?.symbol || props.distanceOptions[0].symbol);
12
+
13
+ const handleCurrencyChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
14
+ const selectedDistanceUnit = props.distanceOptions.find(option => option.name === e.target.value);
15
+ if (selectedDistanceUnit) {
16
+ if(inputValue != undefined && !Number.isNaN(inputValue as number)){
17
+ const convertedDistance = convertDistance(inputValue as number,currentDistanceUnit,selectedDistanceUnit.symbol)
18
+ setInputValue(convertedDistance);
19
+ }
20
+ setCurrentDistanceUnit(selectedDistanceUnit.symbol);
21
+ }
22
+ }
23
+
24
+ function convertDistance(dist:number,previousUnit:string,newUnit:string){
25
+ if(previousUnit == DistanceUnit.KILOMETER && newUnit == DistanceUnit.MILE){
26
+ return kilometersToMiles(dist);
27
+ } else if (previousUnit == DistanceUnit.MILE && newUnit == DistanceUnit.KILOMETER){
28
+ return milesToKilometers(dist);
29
+ } else if (previousUnit == DistanceUnit.KILOMETER && newUnit == DistanceUnit.METER){
30
+ return dist * 1000;
31
+ } else if (previousUnit == DistanceUnit.METER && newUnit == DistanceUnit.KILOMETER){
32
+ return dist / 1000;
33
+ } else if (previousUnit == DistanceUnit.METER && newUnit == DistanceUnit.MILE){
34
+ return metersToMiles(dist);
35
+ } else if (previousUnit == DistanceUnit.MILE && newUnit == DistanceUnit.METER){
36
+ return milesToMeters(dist);
37
+ } else {
38
+ return dist;
39
+ }
40
+ }
41
+
42
+ function handleChange(e: number){
43
+ if(Number.isNaN(e)){
44
+ setInputValue("")
45
+ if(props.onChange){
46
+ props.onChange(0)
47
+ }
48
+ }else{
49
+ setInputValue(e)
50
+ const convertedDistance = convertDistance(e,currentDistanceUnit,DistanceUnit.MILE)
51
+ if(props.onChange){
52
+ props.onChange(convertedDistance)
53
+ }
54
+ }
55
+ }
56
+
57
+ return(
58
+ <div className="distance-input-container">
59
+ { props.label != undefined && <div className="label"><label>{props.label}</label></div>}
60
+ <div className={"flex-container" + (props.error ? " error" : "")}>
61
+ <input
62
+ type='number'
63
+ value={inputValue}
64
+ min={props.min}
65
+ max={props.max}
66
+ onChange={(e) => handleChange(e.target.valueAsNumber)}
67
+ step={props.step}/>
68
+ <div className='distance-options'>
69
+ <select
70
+ id='distance'
71
+ name='distance'
72
+ onChange={handleCurrencyChange}>
73
+ {props.distanceOptions.map(el => <option style={{borderRadius:0}} key={el.name}>{el.name}</option>)}
74
+ </select>
75
+ </div>
76
+ </div>
77
+ { props.error && <span style={{
78
+ color: "rgb(211,47,47)",
79
+ fontSize:'0.75rem',
80
+ margin:'0 14px',
81
+ marginTop:'3px',
82
+ display:'inline-block',
83
+ width:'calc(100%-28px)',
84
+ lineHeight: 1.66,
85
+ letterSpacing: '0.03333em',
86
+ fontFamily:'"Roboto","Helvetica","Arial",sans-serif' }}>
87
+ {props.errorMessage}
88
+ </span>}
89
+ </div>
90
+ );
91
+ }
92
+
93
+ export default DistanceInput
@@ -0,0 +1,41 @@
1
+ export const MILES_TO_METERS_FACTOR = 1609.344;
2
+
3
+ export function milesToMeters(mi:number){
4
+ return mi * MILES_TO_METERS_FACTOR;
5
+ }
6
+
7
+ export function metersToMiles(m:number){
8
+ return m / MILES_TO_METERS_FACTOR;
9
+ }
10
+
11
+ export function milesToKilometers(mi:number){
12
+ return milesToMeters(mi) / 1000;
13
+ }
14
+
15
+ export function kilometersToMiles(km:number){
16
+ return 1000 * metersToMiles(km);
17
+ }
18
+
19
+ export enum DistanceUnit{
20
+ METER = 'm',
21
+ MILE = 'mi',
22
+ KILOMETER = 'km'
23
+ }
24
+
25
+ export interface DistanceOption {
26
+ name: string;
27
+ symbol: string;
28
+ }
29
+
30
+ export interface DistanceInputProps{
31
+ error?: boolean,
32
+ errorMessage?: string,
33
+ label?: string,
34
+ min?: number,
35
+ max?: number,
36
+ step?: number,
37
+ value?: number | string,
38
+ unit?: DistanceOption,
39
+ distanceOptions: DistanceOption[];
40
+ onChange?: (e: number) => void
41
+ }
@@ -0,0 +1,26 @@
1
+ .file-input-container{
2
+ padding:0;
3
+ font-family: system-ui, sans-serif;
4
+ }
5
+
6
+ .file-input-container .label{
7
+ font-weight: 700;
8
+ padding-bottom: 5px;
9
+ }
10
+
11
+ .file-input-container .file-input{
12
+ width:90px
13
+ }
14
+
15
+ .file-input-container .file-upload {
16
+ display: inline-flex;
17
+ position: relative;
18
+ left: 10px;
19
+ background-color: #f4f6f8;
20
+ font-size: 0.9em;
21
+ line-height: 1.6em;
22
+ }
23
+
24
+ .file-input-container .file-upload.selected{
25
+ color: green;
26
+ }
@@ -0,0 +1,20 @@
1
+ import { StoryFn, Meta } from "@storybook/react";
2
+ import FileInput from "./file-input";
3
+
4
+ export default {
5
+ title: "ReactComponentLibrary/Inputs",
6
+ component: FileInput,
7
+ } as Meta<typeof FileInput>;
8
+
9
+ const Template: StoryFn<typeof FileInput> = (args) => <FileInput {...args} />;
10
+ let fileChosen = false;
11
+
12
+ export const FileInputTest = Template.bind({});
13
+ FileInputTest.args = {
14
+ error:false,
15
+ errorMessage:"Must upload a valid payout CSV or JSON",
16
+ value:undefined,
17
+ onChange:(() => fileChosen = true),
18
+ isFileSelected:fileChosen,
19
+ accept:".csv,.json"
20
+ };
@@ -0,0 +1,43 @@
1
+ "use client"
2
+
3
+ import React, { ChangeEvent } from "react";
4
+ import "./file-input.css"
5
+ import { FileInputProps } from "./file-input.types";
6
+
7
+ function InputFile(props: FileInputProps){
8
+
9
+ const [file, setFile] = React.useState(props.value)
10
+
11
+ function handleChange(event: ChangeEvent<HTMLInputElement>) {
12
+ if(event.target.files != null){
13
+ setFile(event.target.files[0])
14
+ if(props.onChange != undefined){
15
+ props.onChange(event.target.files[0])
16
+ }
17
+ }
18
+ }
19
+
20
+ return(
21
+ <div className={"file-input-container" + (props.error ? " error" : "")}>
22
+ { props.label != undefined && <div className="label"><label>{props.label}</label></div>}
23
+ <label>
24
+ <input className="file-input" disabled={props.disabled} type="file" onChange={handleChange} accept={props.accept}></input>
25
+ <span className={"file-upload" + (props.isFileSelected ? " selected" : "")}>{props.isFileSelected ? 'File uploaded!' : 'No file chosen'}</span>
26
+ </label>
27
+ { props.error && <span style={{
28
+ color: "rgb(211,47,47)",
29
+ fontSize:'0.75rem',
30
+ margin:'0 14px',
31
+ marginTop:'3px',
32
+ display:'inline-block',
33
+ width:'calc(100%-28px)',
34
+ lineHeight: 1.66,
35
+ letterSpacing: '0.03333em',
36
+ fontFamily:'"Roboto","Helvetica","Arial",sans-serif' }}>
37
+ {props.errorMessage}
38
+ </span>}
39
+ </div>
40
+ );
41
+ }
42
+
43
+ export default InputFile
@@ -0,0 +1,10 @@
1
+ export interface FileInputProps {
2
+ error?: boolean,
3
+ errorMessage?: string,
4
+ label?: string,
5
+ disabled?: boolean,
6
+ value?: File,
7
+ onChange?: (e: File) => void
8
+ accept?: string,
9
+ isFileSelected?: boolean
10
+ }