@aclymatepackages/modules 1.0.0
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/README.md +43 -0
- package/babel.config.json +18 -0
- package/dist/components/CompaniesAutocomplete.js +126 -0
- package/dist/components/CompanyOnboardingInput.js +116 -0
- package/dist/components/ComparisonChart.js +200 -0
- package/dist/components/CustomTooltipDisplayRow.js +59 -0
- package/dist/components/EmissionsChart.js +467 -0
- package/dist/components/EmissionsCustomTooltip.js +181 -0
- package/dist/components/EmissionsPieChart.js +130 -0
- package/dist/components/EmissionsReductionGraph.js +118 -0
- package/dist/components/FootprintEquivalencies.js +181 -0
- package/dist/components/FootprintVideo.js +262 -0
- package/dist/components/FuelTypesSelect.js +36 -0
- package/dist/components/IndustryAutocomplete.js +58 -0
- package/dist/components/PlacesAutocomplete.js +173 -0
- package/dist/components/StripeElements.js +95 -0
- package/dist/components/YesNoQuestion.js +88 -0
- package/dist/components/stripeInput.js +293 -0
- package/dist/components/useChartWarningLabels.js +143 -0
- package/dist/index.js +118 -0
- package/package.json +74 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +43 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/src/components/CompaniesAutocomplete.js +125 -0
- package/src/components/CompanyOnboardingInput.js +113 -0
- package/src/components/ComparisonChart.js +236 -0
- package/src/components/CustomTooltipDisplayRow.js +41 -0
- package/src/components/EmissionsChart.js +579 -0
- package/src/components/EmissionsCustomTooltip.js +146 -0
- package/src/components/EmissionsPieChart.js +120 -0
- package/src/components/EmissionsReductionGraph.js +107 -0
- package/src/components/FootprintEquivalencies.js +203 -0
- package/src/components/FootprintVideo.js +328 -0
- package/src/components/FuelTypesSelect.js +29 -0
- package/src/components/IndustryAutocomplete.js +56 -0
- package/src/components/PlacesAutocomplete.js +174 -0
- package/src/components/StripeElements.js +95 -0
- package/src/components/YesNoQuestion.js +68 -0
- package/src/components/stripeInput.js +288 -0
- package/src/components/useChartWarningLabels.js +139 -0
- package/src/index.js +35 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from "react";
|
|
2
|
+
import CountUp from "react-countup";
|
|
3
|
+
import Typewriter from "typewriter-effect";
|
|
4
|
+
|
|
5
|
+
import { ThemeProvider } from "@mui/material/styles";
|
|
6
|
+
import { Box, Container, Grid, Typography, Paper, Fade } from "@mui/material";
|
|
7
|
+
|
|
8
|
+
import { AnimatedLogo } from "@aclymatepackages/atoms";
|
|
9
|
+
|
|
10
|
+
import { editObjectData } from "@aclymatepackages/array-immutability-helpers";
|
|
11
|
+
import { mergeDarkTheme } from "@aclymatepackages/themes";
|
|
12
|
+
import {
|
|
13
|
+
tonsToLbs,
|
|
14
|
+
convertFootprintToDriving,
|
|
15
|
+
convertFootprintToIce,
|
|
16
|
+
convertFootprintToTrees,
|
|
17
|
+
} from "@aclymatepackages/converters";
|
|
18
|
+
import { letterSBoolean } from "@aclymatepackages/formatters";
|
|
19
|
+
|
|
20
|
+
const OnboardingFootprintLayout = ({ children }) => {
|
|
21
|
+
const [backgroundFade, setBackgroundFade] = useState(false);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const fadeBackground = setTimeout(() => setBackgroundFade(true), 1500);
|
|
25
|
+
return () => clearTimeout(fadeBackground);
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Box
|
|
30
|
+
style={{
|
|
31
|
+
position: "absolute",
|
|
32
|
+
top: 0,
|
|
33
|
+
left: 0,
|
|
34
|
+
bottom: 0,
|
|
35
|
+
right: 0,
|
|
36
|
+
backgroundColor: "rgba(0, 0, 0, 0.4)",
|
|
37
|
+
opacity: backgroundFade ? 1 : 0,
|
|
38
|
+
transition: "opacity 1s",
|
|
39
|
+
}}
|
|
40
|
+
display="flex"
|
|
41
|
+
alignItems="center"
|
|
42
|
+
justifyContent="center"
|
|
43
|
+
p={4}
|
|
44
|
+
>
|
|
45
|
+
{backgroundFade && (
|
|
46
|
+
<ThemeProvider theme={mergeDarkTheme}>{children}</ThemeProvider>
|
|
47
|
+
)}
|
|
48
|
+
</Box>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const VideoFootprintDisplay = ({ monthlyFootprintTons, onAdvanceSlide }) => {
|
|
53
|
+
const [showCaption, setShowCaption] = useState(false);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const advanceSlide = setTimeout(onAdvanceSlide, 8500);
|
|
56
|
+
return () => clearTimeout(advanceSlide);
|
|
57
|
+
}, [onAdvanceSlide]);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<OnboardingFootprintLayout>
|
|
61
|
+
<Grid container direction="column" spacing={2}>
|
|
62
|
+
<Grid item>
|
|
63
|
+
<CountUp
|
|
64
|
+
delay={0}
|
|
65
|
+
start={0}
|
|
66
|
+
startOnMount
|
|
67
|
+
end={Math.round(tonsToLbs(monthlyFootprintTons))}
|
|
68
|
+
separator=","
|
|
69
|
+
decimal="."
|
|
70
|
+
suffix=" lbs."
|
|
71
|
+
duration={1.5}
|
|
72
|
+
onEnd={() => setShowCaption(true)}
|
|
73
|
+
>
|
|
74
|
+
{({ countUpRef }) => (
|
|
75
|
+
<Typography
|
|
76
|
+
ref={countUpRef}
|
|
77
|
+
variant="h2"
|
|
78
|
+
align="center"
|
|
79
|
+
color="textPrimary"
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
82
|
+
</CountUp>
|
|
83
|
+
</Grid>
|
|
84
|
+
{showCaption && (
|
|
85
|
+
<Grid item>
|
|
86
|
+
<Typography variant="h5" align="center" color="textSecondary">
|
|
87
|
+
<Typewriter
|
|
88
|
+
options={{
|
|
89
|
+
strings: [
|
|
90
|
+
"This is our preliminary estimate of your company's monthly carbon emissions.",
|
|
91
|
+
],
|
|
92
|
+
autoStart: true,
|
|
93
|
+
delay: 50,
|
|
94
|
+
deleteSpeed: 100000,
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
</Typography>
|
|
98
|
+
</Grid>
|
|
99
|
+
)}
|
|
100
|
+
</Grid>
|
|
101
|
+
</OnboardingFootprintLayout>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const FootprintVideoIntermediateStep = ({ label, onAdvanceSlide }) => {
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
const advanceSlide = setTimeout(onAdvanceSlide, 8000);
|
|
108
|
+
return () => clearTimeout(advanceSlide);
|
|
109
|
+
}, [onAdvanceSlide]);
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<OnboardingFootprintLayout video="logging">
|
|
113
|
+
<Typography variant="h5" align="center" color="textSecondary">
|
|
114
|
+
<Typewriter
|
|
115
|
+
options={{
|
|
116
|
+
strings: [label],
|
|
117
|
+
autoStart: true,
|
|
118
|
+
delay: 50,
|
|
119
|
+
deleteSpeed: 100000,
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
</Typography>
|
|
123
|
+
</OnboardingFootprintLayout>
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const FootprintVideoFinal = ({ actionButton, onVideoEnd }) => {
|
|
128
|
+
const [showButton, setShowButton] = useState(false);
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
const onShowButton = setTimeout(() => {
|
|
132
|
+
setShowButton(true);
|
|
133
|
+
if (onVideoEnd) {
|
|
134
|
+
onVideoEnd();
|
|
135
|
+
}
|
|
136
|
+
}, 9000);
|
|
137
|
+
|
|
138
|
+
return () => clearTimeout(onShowButton);
|
|
139
|
+
}, [onVideoEnd]);
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<OnboardingFootprintLayout>
|
|
143
|
+
<Grid container direction="column" spacing={2} alignItems="center">
|
|
144
|
+
<Grid item>
|
|
145
|
+
<Typography variant="h5" align="center" color="textSecondary">
|
|
146
|
+
<Typewriter
|
|
147
|
+
onInit={(typewriter) =>
|
|
148
|
+
typewriter
|
|
149
|
+
.changeDelay(50)
|
|
150
|
+
.typeString("But all of that ends now.")
|
|
151
|
+
.pauseFor(1500)
|
|
152
|
+
.typeString(" Welcome to your climate journey.")
|
|
153
|
+
.start()
|
|
154
|
+
}
|
|
155
|
+
/>
|
|
156
|
+
</Typography>
|
|
157
|
+
</Grid>
|
|
158
|
+
<Fade in={showButton}>
|
|
159
|
+
<Grid item>{actionButton}</Grid>
|
|
160
|
+
</Fade>
|
|
161
|
+
</Grid>
|
|
162
|
+
</OnboardingFootprintLayout>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const FootprintVideo = ({
|
|
167
|
+
monthlyFootprintTons,
|
|
168
|
+
footprintLoading,
|
|
169
|
+
actionButton,
|
|
170
|
+
onVideoEnd,
|
|
171
|
+
}) => {
|
|
172
|
+
const [videoStep, setVideoStep] = useState(0);
|
|
173
|
+
const [loadedVideos, setLoadedVideos] = useState({
|
|
174
|
+
pollution: false,
|
|
175
|
+
driving: false,
|
|
176
|
+
logging: false,
|
|
177
|
+
glacier: false,
|
|
178
|
+
monthly: false,
|
|
179
|
+
mountains: false,
|
|
180
|
+
});
|
|
181
|
+
const [showVideos, setShowVideos] = useState(false);
|
|
182
|
+
|
|
183
|
+
const onVideoLoaded = (video) => editObjectData(setLoadedVideos, video, true);
|
|
184
|
+
|
|
185
|
+
const pollutionVideoRef = useRef();
|
|
186
|
+
const drivingVideoRef = useRef();
|
|
187
|
+
const loggingVideoRef = useRef();
|
|
188
|
+
const glacierVideoRef = useRef();
|
|
189
|
+
const monthlyVideoRef = useRef();
|
|
190
|
+
const mountainsVideoRef = useRef();
|
|
191
|
+
|
|
192
|
+
const videoRefsArray = [
|
|
193
|
+
pollutionVideoRef,
|
|
194
|
+
drivingVideoRef,
|
|
195
|
+
loggingVideoRef,
|
|
196
|
+
glacierVideoRef,
|
|
197
|
+
monthlyVideoRef,
|
|
198
|
+
mountainsVideoRef,
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
const areAllVideosLoaded = Object.values(loadedVideos).reduce(
|
|
203
|
+
(acc, isLoaded) => isLoaded && acc,
|
|
204
|
+
true
|
|
205
|
+
);
|
|
206
|
+
if (areAllVideosLoaded && !footprintLoading) {
|
|
207
|
+
setShowVideos(true);
|
|
208
|
+
}
|
|
209
|
+
}, [loadedVideos, footprintLoading]);
|
|
210
|
+
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
if (
|
|
213
|
+
pollutionVideoRef.current &&
|
|
214
|
+
loggingVideoRef.current &&
|
|
215
|
+
glacierVideoRef.current &&
|
|
216
|
+
drivingVideoRef.current &&
|
|
217
|
+
monthlyVideoRef.current &&
|
|
218
|
+
mountainsVideoRef.current &&
|
|
219
|
+
showVideos
|
|
220
|
+
) {
|
|
221
|
+
videoRefsArray[videoStep]?.current?.play();
|
|
222
|
+
}
|
|
223
|
+
// eslint-disable-next-line
|
|
224
|
+
}, [videoStep, showVideos]);
|
|
225
|
+
|
|
226
|
+
const onAdvanceSlide = () => setVideoStep((currentStep) => currentStep + 1);
|
|
227
|
+
|
|
228
|
+
const intermediateStepLabels = [
|
|
229
|
+
`That's as much carbon as driving ${convertFootprintToDriving(
|
|
230
|
+
monthlyFootprintTons
|
|
231
|
+
)} miles.`,
|
|
232
|
+
`Or cutting down more than ${convertFootprintToTrees(
|
|
233
|
+
monthlyFootprintTons
|
|
234
|
+
)} 50-year-old tree${letterSBoolean(
|
|
235
|
+
convertFootprintToTrees(monthlyFootprintTons)
|
|
236
|
+
)}.`,
|
|
237
|
+
`It's enough to melt more than ${formatDecimal(
|
|
238
|
+
convertFootprintToIce(monthlyFootprintTons)
|
|
239
|
+
)} square feet of Arctic sea ice that will never come back.`,
|
|
240
|
+
"And your company emits this much carbon every single month.",
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
const onboardingFootprintSteps = [
|
|
244
|
+
<VideoFootprintDisplay
|
|
245
|
+
monthlyFootprintTons={monthlyFootprintTons}
|
|
246
|
+
onAdvanceSlide={onAdvanceSlide}
|
|
247
|
+
/>,
|
|
248
|
+
...intermediateStepLabels.map((label, idx) => (
|
|
249
|
+
<FootprintVideoIntermediateStep
|
|
250
|
+
label={label}
|
|
251
|
+
onAdvanceSlide={onAdvanceSlide}
|
|
252
|
+
key={`video-intermediate-step-${idx}`}
|
|
253
|
+
/>
|
|
254
|
+
)),
|
|
255
|
+
<FootprintVideoFinal actionButton={actionButton} onVideoEnd={onVideoEnd} />,
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
const buildVideoProps = (idx) => ({
|
|
259
|
+
autoPlay: !idx,
|
|
260
|
+
style: {
|
|
261
|
+
position: "absolute",
|
|
262
|
+
top: 0,
|
|
263
|
+
objectFit: "cover",
|
|
264
|
+
objectPosition: "center center",
|
|
265
|
+
opacity: showVideos && idx === videoStep ? 1 : 0,
|
|
266
|
+
transition: "opacity 1s",
|
|
267
|
+
marginBottom: "-10px",
|
|
268
|
+
width: "100%",
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const buildSrcProps = (video) => ({
|
|
273
|
+
src: `/videos/${video}.mp4`,
|
|
274
|
+
type: "video/mp4",
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const videos = [
|
|
278
|
+
"pollution",
|
|
279
|
+
"driving",
|
|
280
|
+
"logging",
|
|
281
|
+
"glacier",
|
|
282
|
+
"monthly",
|
|
283
|
+
"mountains",
|
|
284
|
+
];
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<Container maxWidth="md">
|
|
288
|
+
<Paper style={{ overflow: "hidden" }}>
|
|
289
|
+
<div
|
|
290
|
+
style={{
|
|
291
|
+
position: "relative",
|
|
292
|
+
paddingTop: "56.25%",
|
|
293
|
+
}}
|
|
294
|
+
>
|
|
295
|
+
{!showVideos && (
|
|
296
|
+
<Box
|
|
297
|
+
display="flex"
|
|
298
|
+
alignItems="center"
|
|
299
|
+
justifyContent="center"
|
|
300
|
+
style={{
|
|
301
|
+
position: "absolute",
|
|
302
|
+
top: 0,
|
|
303
|
+
left: 0,
|
|
304
|
+
bottom: 0,
|
|
305
|
+
right: 0,
|
|
306
|
+
}}
|
|
307
|
+
>
|
|
308
|
+
<AnimatedLogo />
|
|
309
|
+
</Box>
|
|
310
|
+
)}
|
|
311
|
+
{videos.map((video, idx) => (
|
|
312
|
+
<video
|
|
313
|
+
key={`footprint-video-${idx}`}
|
|
314
|
+
muted
|
|
315
|
+
{...buildVideoProps(idx)}
|
|
316
|
+
ref={videoRefsArray[idx]}
|
|
317
|
+
onLoadedData={() => onVideoLoaded(video)}
|
|
318
|
+
>
|
|
319
|
+
<source {...buildSrcProps(video)} />
|
|
320
|
+
</video>
|
|
321
|
+
))}
|
|
322
|
+
{onboardingFootprintSteps[videoStep]}
|
|
323
|
+
</div>
|
|
324
|
+
</Paper>
|
|
325
|
+
</Container>
|
|
326
|
+
);
|
|
327
|
+
};
|
|
328
|
+
export default FootprintVideo;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { Select } from "@aclymatepackages/atoms";
|
|
4
|
+
import { ucFirstLetters } from "@aclymatepackages/formatters";
|
|
5
|
+
|
|
6
|
+
const FuelTypesSelect = ({
|
|
7
|
+
fuelType,
|
|
8
|
+
editVehicle,
|
|
9
|
+
availableFuelTypes,
|
|
10
|
+
id,
|
|
11
|
+
smallFont,
|
|
12
|
+
variant,
|
|
13
|
+
noLabel,
|
|
14
|
+
}) => (
|
|
15
|
+
<Select
|
|
16
|
+
id={id}
|
|
17
|
+
size="small"
|
|
18
|
+
label={noLabel || "Fuel Type"}
|
|
19
|
+
value={fuelType || ""}
|
|
20
|
+
editValue={editVehicle}
|
|
21
|
+
options={availableFuelTypes.map((option) => ({
|
|
22
|
+
label: ucFirstLetters(option),
|
|
23
|
+
value: option,
|
|
24
|
+
}))}
|
|
25
|
+
smallFont={smallFont}
|
|
26
|
+
variant={variant}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
export default FuelTypesSelect;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
|
|
3
|
+
import Autocomplete from "@mui/lab/Autocomplete";
|
|
4
|
+
import { TextField, Popper } from "@mui/material";
|
|
5
|
+
|
|
6
|
+
import { industriesList } from "@aclymatepackages/lists";
|
|
7
|
+
|
|
8
|
+
const CustomPopper = (props) => (
|
|
9
|
+
<Popper {...props} style={{ width: "fit-content" }} />
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const IndustryAutocomplete = ({
|
|
13
|
+
industry,
|
|
14
|
+
setIndustry,
|
|
15
|
+
style,
|
|
16
|
+
variant = "outlined",
|
|
17
|
+
label,
|
|
18
|
+
helperText,
|
|
19
|
+
textFieldProps,
|
|
20
|
+
}) => {
|
|
21
|
+
const [inputValue, setInputValue] = useState("");
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
onClick={(e) => e.stopPropagation()}
|
|
26
|
+
onFocus={(e) => e.stopPropagation()}
|
|
27
|
+
>
|
|
28
|
+
<Autocomplete
|
|
29
|
+
id="company-industry-autocomplete"
|
|
30
|
+
options={industriesList}
|
|
31
|
+
getOptionLabel={(option) =>
|
|
32
|
+
typeof option === "string" ? option : option.label
|
|
33
|
+
}
|
|
34
|
+
renderInput={(params) => (
|
|
35
|
+
<TextField
|
|
36
|
+
{...params}
|
|
37
|
+
label={label || "Company Industry"}
|
|
38
|
+
fullWidth
|
|
39
|
+
size="small"
|
|
40
|
+
variant={variant}
|
|
41
|
+
style={style}
|
|
42
|
+
helperText={helperText}
|
|
43
|
+
{...textFieldProps}
|
|
44
|
+
/>
|
|
45
|
+
)}
|
|
46
|
+
inputValue={inputValue}
|
|
47
|
+
onInputChange={(e, newInputValue) => setInputValue(newInputValue)}
|
|
48
|
+
onChange={(event, newValue) => setIndustry(newValue || null)}
|
|
49
|
+
value={industry ?? null}
|
|
50
|
+
getOptionSelected={(option, value) => option.label === value.label}
|
|
51
|
+
PopperComponent={CustomPopper}
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
export default IndustryAutocomplete;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import parse from "autosuggest-highlight/parse";
|
|
3
|
+
|
|
4
|
+
import Autocomplete from "@mui/lab/Autocomplete";
|
|
5
|
+
import { Grid, Typography, TextField } from "@mui/material";
|
|
6
|
+
|
|
7
|
+
import fetchAclymateApi from "@aclymatepackages/fetch-aclymate-api";
|
|
8
|
+
|
|
9
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
10
|
+
import { faMapMarkerAlt } from "@fortawesome/free-solid-svg-icons";
|
|
11
|
+
|
|
12
|
+
const PlacesAutocomplete = ({
|
|
13
|
+
place,
|
|
14
|
+
editPlace,
|
|
15
|
+
label,
|
|
16
|
+
errorMsg,
|
|
17
|
+
variant = "outlined",
|
|
18
|
+
size = "medium",
|
|
19
|
+
disabled,
|
|
20
|
+
helperText,
|
|
21
|
+
error,
|
|
22
|
+
id,
|
|
23
|
+
style,
|
|
24
|
+
textFieldProps,
|
|
25
|
+
coordinates,
|
|
26
|
+
}) => {
|
|
27
|
+
const [inputValue, setInputValue] = useState("");
|
|
28
|
+
const [options, setOptions] = useState([]);
|
|
29
|
+
const [previousCharacterCount, setPreviousCharacterCount] = useState(0);
|
|
30
|
+
|
|
31
|
+
const fetchPlaces = useCallback(
|
|
32
|
+
async ({ value, signal }) => {
|
|
33
|
+
const formattedPlaceValue = (value || inputValue).replaceAll(
|
|
34
|
+
/#|\?/gm,
|
|
35
|
+
""
|
|
36
|
+
);
|
|
37
|
+
const placeObj = { place: formattedPlaceValue };
|
|
38
|
+
const coordinatesObj = coordinates ? { coordinates } : {};
|
|
39
|
+
const dataString = JSON.stringify({ ...placeObj, ...coordinatesObj });
|
|
40
|
+
|
|
41
|
+
const placesFetchCallback = (data) => {
|
|
42
|
+
setOptions(data.predictions || []);
|
|
43
|
+
return data.predictions;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return await fetchAclymateApi({
|
|
47
|
+
path: `/google-maps/autocomplete/${dataString}`,
|
|
48
|
+
method: "GET",
|
|
49
|
+
signal,
|
|
50
|
+
callback: placesFetchCallback,
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
[inputValue, coordinates]
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (inputValue === "") {
|
|
58
|
+
setOptions(place ? [place] : []);
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const { signal } = controller;
|
|
63
|
+
|
|
64
|
+
fetchPlaces({ signal });
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
controller.abort();
|
|
68
|
+
};
|
|
69
|
+
}, [inputValue, place, fetchPlaces]);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Autocomplete
|
|
73
|
+
id={id || "places1-autocomplete"}
|
|
74
|
+
options={options}
|
|
75
|
+
getOptionLabel={(option) =>
|
|
76
|
+
typeof option === "string"
|
|
77
|
+
? option
|
|
78
|
+
: option.description || option.address || ""
|
|
79
|
+
}
|
|
80
|
+
filterOptions={(x) => x}
|
|
81
|
+
autoComplete
|
|
82
|
+
filterSelectedOptions
|
|
83
|
+
includeInputInList
|
|
84
|
+
disabled={disabled}
|
|
85
|
+
renderInput={(params) => (
|
|
86
|
+
<TextField
|
|
87
|
+
id="places-autocomplete-input"
|
|
88
|
+
{...params}
|
|
89
|
+
label={label}
|
|
90
|
+
variant={variant}
|
|
91
|
+
error={(errorMsg && !place) || error}
|
|
92
|
+
size={size}
|
|
93
|
+
autoComplete="off"
|
|
94
|
+
helperText={helperText}
|
|
95
|
+
style={style}
|
|
96
|
+
{...textFieldProps}
|
|
97
|
+
/>
|
|
98
|
+
)}
|
|
99
|
+
getOptionSelected={(option, value) => {
|
|
100
|
+
return (
|
|
101
|
+
option === "" ||
|
|
102
|
+
option.description === value.description ||
|
|
103
|
+
option === value
|
|
104
|
+
);
|
|
105
|
+
}}
|
|
106
|
+
value={place || ""}
|
|
107
|
+
onChange={(event, newValue) => {
|
|
108
|
+
setOptions(newValue ? [newValue, ...options] : options);
|
|
109
|
+
editPlace(newValue);
|
|
110
|
+
}}
|
|
111
|
+
onInputChange={(event, newInputValue) => {
|
|
112
|
+
setInputValue(newInputValue);
|
|
113
|
+
if (
|
|
114
|
+
newInputValue &&
|
|
115
|
+
Math.abs(previousCharacterCount - newInputValue.length) > 1 &&
|
|
116
|
+
newInputValue !== place?.description
|
|
117
|
+
) {
|
|
118
|
+
fetchPlaces({ value: newInputValue }).then((options = []) => {
|
|
119
|
+
const firstOption = options[0];
|
|
120
|
+
if (firstOption) {
|
|
121
|
+
const { place_id, description } = firstOption;
|
|
122
|
+
setInputValue(description);
|
|
123
|
+
editPlace({ place_id, description });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
setPreviousCharacterCount(newInputValue.length);
|
|
128
|
+
}}
|
|
129
|
+
renderOption={(props, option) => {
|
|
130
|
+
if (option.description) {
|
|
131
|
+
const matches =
|
|
132
|
+
option?.structured_formatting?.main_text_matched_substrings;
|
|
133
|
+
const parts = !!matches?.length
|
|
134
|
+
? parse(
|
|
135
|
+
option?.structured_formatting?.main_text,
|
|
136
|
+
matches.map((match) => [
|
|
137
|
+
match.offset,
|
|
138
|
+
match.offset + match.length,
|
|
139
|
+
])
|
|
140
|
+
)
|
|
141
|
+
: [];
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<li {...props}>
|
|
145
|
+
<Grid container alignItems="center" spacing={2}>
|
|
146
|
+
<Grid item>
|
|
147
|
+
<FontAwesomeIcon icon={faMapMarkerAlt} />
|
|
148
|
+
</Grid>
|
|
149
|
+
<Grid item xs>
|
|
150
|
+
{parts.length
|
|
151
|
+
? parts.map((part, index) => (
|
|
152
|
+
<span
|
|
153
|
+
key={index}
|
|
154
|
+
style={{ fontWeight: part.highlight ? 700 : 400 }}
|
|
155
|
+
>
|
|
156
|
+
{part.text}
|
|
157
|
+
</span>
|
|
158
|
+
))
|
|
159
|
+
: ""}
|
|
160
|
+
<Typography variant="body2" color="textSecondary">
|
|
161
|
+
{option?.structured_formatting?.secondary_text}
|
|
162
|
+
</Typography>
|
|
163
|
+
</Grid>
|
|
164
|
+
</Grid>
|
|
165
|
+
</li>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return <li {...props}>{option}</li>;
|
|
170
|
+
}}
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
export default PlacesAutocomplete;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
import { Elements } from "@stripe/react-stripe-js";
|
|
4
|
+
import { loadStripe } from "@stripe/stripe-js";
|
|
5
|
+
|
|
6
|
+
import { Grid } from "@mui/material";
|
|
7
|
+
|
|
8
|
+
import fetchAclymateApi from "@aclymatepackages/fetch-aclymate-api";
|
|
9
|
+
|
|
10
|
+
const StripeElementsWrapper = ({
|
|
11
|
+
accountData,
|
|
12
|
+
accountCollection,
|
|
13
|
+
loadingComponent,
|
|
14
|
+
baseFetchHost,
|
|
15
|
+
stripePromise,
|
|
16
|
+
children,
|
|
17
|
+
}) => {
|
|
18
|
+
const [stripeClientSecret, setStripeClientSecret] = useState("");
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
id: accountId,
|
|
22
|
+
stripeCustomerId: accountDataCustomerId,
|
|
23
|
+
billing = {},
|
|
24
|
+
} = accountData;
|
|
25
|
+
const { stripeCustomerId: billingObjStripeCustomerId } = billing;
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const getStripeClientSecret = async () => {
|
|
29
|
+
const { clientSecret } = await fetchAclymateApi({
|
|
30
|
+
path: `/stripe/create-setup-intent`,
|
|
31
|
+
method: "POST",
|
|
32
|
+
data: {
|
|
33
|
+
accountId,
|
|
34
|
+
accountType: accountCollection,
|
|
35
|
+
customerId: billingObjStripeCustomerId || accountDataCustomerId,
|
|
36
|
+
},
|
|
37
|
+
callback: (response) => response,
|
|
38
|
+
baseFetchHost,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
setStripeClientSecret(clientSecret);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (!stripeClientSecret) {
|
|
45
|
+
getStripeClientSecret();
|
|
46
|
+
}
|
|
47
|
+
}, [
|
|
48
|
+
stripeClientSecret,
|
|
49
|
+
accountDataCustomerId,
|
|
50
|
+
accountId,
|
|
51
|
+
billingObjStripeCustomerId,
|
|
52
|
+
accountCollection,
|
|
53
|
+
baseFetchHost,
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
const appearance = {
|
|
57
|
+
theme: "stripe",
|
|
58
|
+
variables: {
|
|
59
|
+
colorPrimary: "#27aae1",
|
|
60
|
+
colorBackground: "fafafa",
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
{!stripeClientSecret ? (
|
|
67
|
+
loadingComponent ? (
|
|
68
|
+
<Grid container justifyContent="center" spacing={2}>
|
|
69
|
+
<Grid item>{loadingComponent}</Grid>
|
|
70
|
+
</Grid>
|
|
71
|
+
) : (
|
|
72
|
+
<></>
|
|
73
|
+
)
|
|
74
|
+
) : (
|
|
75
|
+
<Elements
|
|
76
|
+
stripe={stripePromise}
|
|
77
|
+
options={{ clientSecret: stripeClientSecret, appearance }}
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
</Elements>
|
|
81
|
+
)}
|
|
82
|
+
</>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const StripeElements = ({ stripePublishableKey, children, ...otherProps }) => {
|
|
87
|
+
const stripePromise = loadStripe(stripePublishableKey);
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<StripeElementsWrapper stripePromise={stripePromise} {...otherProps}>
|
|
91
|
+
{children}
|
|
92
|
+
</StripeElementsWrapper>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
export default StripeElements;
|