@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.
Files changed (45) hide show
  1. package/README.md +43 -0
  2. package/babel.config.json +18 -0
  3. package/dist/components/CompaniesAutocomplete.js +126 -0
  4. package/dist/components/CompanyOnboardingInput.js +116 -0
  5. package/dist/components/ComparisonChart.js +200 -0
  6. package/dist/components/CustomTooltipDisplayRow.js +59 -0
  7. package/dist/components/EmissionsChart.js +467 -0
  8. package/dist/components/EmissionsCustomTooltip.js +181 -0
  9. package/dist/components/EmissionsPieChart.js +130 -0
  10. package/dist/components/EmissionsReductionGraph.js +118 -0
  11. package/dist/components/FootprintEquivalencies.js +181 -0
  12. package/dist/components/FootprintVideo.js +262 -0
  13. package/dist/components/FuelTypesSelect.js +36 -0
  14. package/dist/components/IndustryAutocomplete.js +58 -0
  15. package/dist/components/PlacesAutocomplete.js +173 -0
  16. package/dist/components/StripeElements.js +95 -0
  17. package/dist/components/YesNoQuestion.js +88 -0
  18. package/dist/components/stripeInput.js +293 -0
  19. package/dist/components/useChartWarningLabels.js +143 -0
  20. package/dist/index.js +118 -0
  21. package/package.json +74 -0
  22. package/public/favicon.ico +0 -0
  23. package/public/index.html +43 -0
  24. package/public/logo192.png +0 -0
  25. package/public/logo512.png +0 -0
  26. package/public/manifest.json +25 -0
  27. package/public/robots.txt +3 -0
  28. package/src/components/CompaniesAutocomplete.js +125 -0
  29. package/src/components/CompanyOnboardingInput.js +113 -0
  30. package/src/components/ComparisonChart.js +236 -0
  31. package/src/components/CustomTooltipDisplayRow.js +41 -0
  32. package/src/components/EmissionsChart.js +579 -0
  33. package/src/components/EmissionsCustomTooltip.js +146 -0
  34. package/src/components/EmissionsPieChart.js +120 -0
  35. package/src/components/EmissionsReductionGraph.js +107 -0
  36. package/src/components/FootprintEquivalencies.js +203 -0
  37. package/src/components/FootprintVideo.js +328 -0
  38. package/src/components/FuelTypesSelect.js +29 -0
  39. package/src/components/IndustryAutocomplete.js +56 -0
  40. package/src/components/PlacesAutocomplete.js +174 -0
  41. package/src/components/StripeElements.js +95 -0
  42. package/src/components/YesNoQuestion.js +68 -0
  43. package/src/components/stripeInput.js +288 -0
  44. package/src/components/useChartWarningLabels.js +139 -0
  45. 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;