@windstream/react-shared-components 0.2.45 → 0.2.47
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/dist/contentful/index.d.ts +2 -1
- package/dist/contentful/index.esm.js +3 -3
- package/dist/contentful/index.esm.js.map +1 -1
- package/dist/contentful/index.js +2 -2
- package/dist/contentful/index.js.map +1 -1
- package/dist/core.d.ts +151 -3
- package/dist/index.d.ts +152 -4
- package/dist/index.esm.js +6 -6
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/next/index.esm.js +2 -2
- package/dist/next/index.esm.js.map +1 -1
- package/dist/next/index.js +2 -2
- package/dist/next/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.esm.js +1 -1
- package/dist/utils/index.esm.js.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +4 -2
- package/src/components/brand-button/index.tsx +2 -2
- package/src/components/video-link/index.test.tsx +1 -1
- package/src/components/video-link/index.tsx +6 -6
- package/src/contentful/blocks/email-input-block/index.test.tsx +189 -161
- package/src/contentful/blocks/fiber-form/constants.ts +81 -0
- package/src/contentful/blocks/fiber-form/index.tsx +557 -0
- package/src/contentful/blocks/fiber-form/types.ts +92 -0
- package/src/contentful/blocks/primary-hero/index.tsx +1 -1
- package/src/hooks/contentful/use-contentful-rich-text.test.tsx +118 -0
- package/src/hooks/contentful/use-contentful-rich-text.tsx +72 -13
- package/src/index.ts +20 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import { Button } from "../../../components/button";
|
|
5
|
+
import { Input } from "../../../components/input";
|
|
6
|
+
import { MaterialIcon } from "../../../components/material-icon";
|
|
7
|
+
import { Select, type SelectOption } from "../../../components/select";
|
|
8
|
+
import { Text } from "../../../components/text";
|
|
9
|
+
import { Tooltip } from "../../../components/tooltip";
|
|
10
|
+
import { cx } from "../../../utils";
|
|
11
|
+
import {
|
|
12
|
+
fiberFormAddressCollectionFormSchema,
|
|
13
|
+
fiberFormInputInitialValues,
|
|
14
|
+
fiberFormInputList,
|
|
15
|
+
} from "./constants";
|
|
16
|
+
import type {
|
|
17
|
+
FiberAddress,
|
|
18
|
+
FiberAddressCandidate,
|
|
19
|
+
FiberFormErrorProps,
|
|
20
|
+
FiberFormProps,
|
|
21
|
+
FiberFormValues,
|
|
22
|
+
} from "./types";
|
|
23
|
+
import { Field, Form, Formik, type FieldProps } from "formik";
|
|
24
|
+
|
|
25
|
+
const phonePattern = /\D/g;
|
|
26
|
+
const formatPhone = (value: string) => {
|
|
27
|
+
const digits = value.replace(phonePattern, "").slice(0, 10);
|
|
28
|
+
if (digits.length < 4) return digits;
|
|
29
|
+
if (digits.length < 7) return `(${digits.slice(0, 3)}) ${digits.slice(3)}`;
|
|
30
|
+
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const ErrorMessage = ({ name, errors, touched }: FiberFormErrorProps) => {
|
|
34
|
+
const error = errors[name];
|
|
35
|
+
return touched[name] && typeof error === "string" ? (
|
|
36
|
+
<Text className="mt-1 text-text-critical">{error}</Text>
|
|
37
|
+
) : null;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const FieldLabel = ({
|
|
41
|
+
children,
|
|
42
|
+
required = false,
|
|
43
|
+
}: {
|
|
44
|
+
children: React.ReactNode;
|
|
45
|
+
required?: boolean;
|
|
46
|
+
}) => (
|
|
47
|
+
<label className="mb-1 block text-sm font-medium text-text">
|
|
48
|
+
{children}
|
|
49
|
+
{required && <span className="text-text-critical">*</span>}
|
|
50
|
+
</label>
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
export const FiberForm: React.FC<FiberFormProps> = ({
|
|
54
|
+
InputComponent,
|
|
55
|
+
AddressAutocomplete,
|
|
56
|
+
initialValues = fiberFormInputInitialValues,
|
|
57
|
+
inputList = fiberFormInputList,
|
|
58
|
+
formSchema = fiberFormAddressCollectionFormSchema,
|
|
59
|
+
onSubmit,
|
|
60
|
+
lookupAddress,
|
|
61
|
+
buttonText = "See plans",
|
|
62
|
+
buttonClassName,
|
|
63
|
+
bottomTextDisclaimer,
|
|
64
|
+
hideLegalCopy = false,
|
|
65
|
+
checkBoxEnabled = true,
|
|
66
|
+
copyRightLinks,
|
|
67
|
+
className,
|
|
68
|
+
onAddressEvent,
|
|
69
|
+
}) => {
|
|
70
|
+
const FormInput = InputComponent ?? Input;
|
|
71
|
+
const [candidates, setCandidates] = useState<FiberAddressCandidate[]>([]);
|
|
72
|
+
const [showCandidates, setShowCandidates] = useState(false);
|
|
73
|
+
const [unitOptions, setUnitOptions] = useState<SelectOption[]>([]);
|
|
74
|
+
const [selectedUnit, setSelectedUnit] = useState<SelectOption>();
|
|
75
|
+
const [showManualUnit, setShowManualUnit] = useState(false);
|
|
76
|
+
const placeholderBody2Style = {
|
|
77
|
+
fontSize: "1.125rem",
|
|
78
|
+
lineHeight: "1.75rem",
|
|
79
|
+
fontWeight: 500,
|
|
80
|
+
};
|
|
81
|
+
const findCandidates = async (address: string, values: FiberFormValues) => {
|
|
82
|
+
onAddressEvent?.(address);
|
|
83
|
+
if (!lookupAddress || address.length < 3) {
|
|
84
|
+
setCandidates([]);
|
|
85
|
+
setShowCandidates(false);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const result = await lookupAddress(address, values);
|
|
90
|
+
setCandidates(result);
|
|
91
|
+
setShowCandidates(result.length > 0);
|
|
92
|
+
} catch {
|
|
93
|
+
setCandidates([]);
|
|
94
|
+
setShowCandidates(false);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const selectCandidate = (
|
|
99
|
+
candidate: FiberAddressCandidate,
|
|
100
|
+
setFieldValue: (
|
|
101
|
+
field: string,
|
|
102
|
+
value: unknown,
|
|
103
|
+
shouldValidate?: boolean
|
|
104
|
+
) => void
|
|
105
|
+
) => {
|
|
106
|
+
const fullAddress = [
|
|
107
|
+
candidate.addressLine1,
|
|
108
|
+
candidate.city,
|
|
109
|
+
candidate.state,
|
|
110
|
+
candidate.postalCode,
|
|
111
|
+
]
|
|
112
|
+
.filter(Boolean)
|
|
113
|
+
.join(", ");
|
|
114
|
+
const units = (candidate.units ?? []).filter(unit => unit.addressLine2);
|
|
115
|
+
setFieldValue("address", fullAddress);
|
|
116
|
+
setFieldValue("addressToSend", candidate);
|
|
117
|
+
setFieldValue("zip", candidate.postalCode ?? "", false);
|
|
118
|
+
setFieldValue("isManualAddress", false, false);
|
|
119
|
+
setFieldValue("hasUnits", units.length > 0, false);
|
|
120
|
+
setFieldValue(
|
|
121
|
+
"unit",
|
|
122
|
+
units.length > 0 ? "" : (candidate.addressLine2 ?? ""),
|
|
123
|
+
false
|
|
124
|
+
);
|
|
125
|
+
setCandidates([]);
|
|
126
|
+
setShowCandidates(false);
|
|
127
|
+
setSelectedUnit(undefined);
|
|
128
|
+
setUnitOptions([
|
|
129
|
+
...units.map(unit => ({
|
|
130
|
+
label: unit.addressLine2,
|
|
131
|
+
value: unit.dfAddressId ?? "",
|
|
132
|
+
data: unit,
|
|
133
|
+
})),
|
|
134
|
+
...(units.length > 0
|
|
135
|
+
? [
|
|
136
|
+
{ label: "I don't see my unit", value: "manual" },
|
|
137
|
+
{ label: "I don't have a unit", value: "no-unit" },
|
|
138
|
+
]
|
|
139
|
+
: []),
|
|
140
|
+
]);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<Formik
|
|
145
|
+
initialValues={initialValues}
|
|
146
|
+
validationSchema={formSchema}
|
|
147
|
+
onSubmit={async (values, helpers) => {
|
|
148
|
+
await onSubmit(values);
|
|
149
|
+
helpers.resetForm({ values: initialValues });
|
|
150
|
+
setSelectedUnit(undefined);
|
|
151
|
+
setUnitOptions([]);
|
|
152
|
+
setShowManualUnit(false);
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
{({
|
|
156
|
+
errors,
|
|
157
|
+
touched,
|
|
158
|
+
isSubmitting,
|
|
159
|
+
submitCount,
|
|
160
|
+
setFieldValue,
|
|
161
|
+
values,
|
|
162
|
+
}) => (
|
|
163
|
+
<Form className={cx("w-full", className)}>
|
|
164
|
+
<div className="flex flex-col gap-5 sm:flex-row">
|
|
165
|
+
{inputList.firstName && (
|
|
166
|
+
<div className="flex-1">
|
|
167
|
+
<FieldLabel required>First name</FieldLabel>
|
|
168
|
+
<Field
|
|
169
|
+
as={FormInput}
|
|
170
|
+
style={placeholderBody2Style}
|
|
171
|
+
name="firstName"
|
|
172
|
+
placeholder={inputList.firstName.placeholder ?? "First name"}
|
|
173
|
+
/>
|
|
174
|
+
<ErrorMessage
|
|
175
|
+
name="firstName"
|
|
176
|
+
errors={errors}
|
|
177
|
+
touched={touched}
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
{inputList.lastName && (
|
|
182
|
+
<div className="flex-1">
|
|
183
|
+
<FieldLabel required>Last name</FieldLabel>
|
|
184
|
+
<Field
|
|
185
|
+
as={FormInput}
|
|
186
|
+
style={placeholderBody2Style}
|
|
187
|
+
name="lastName"
|
|
188
|
+
placeholder={inputList.lastName.placeholder ?? "Last name"}
|
|
189
|
+
/>
|
|
190
|
+
<ErrorMessage
|
|
191
|
+
name="lastName"
|
|
192
|
+
errors={errors}
|
|
193
|
+
touched={touched}
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
{inputList.address && (
|
|
200
|
+
<div className="mt-8 flex flex-col gap-5 sm:flex-row">
|
|
201
|
+
<div className="sm:flex-[3]">
|
|
202
|
+
<FieldLabel required>Address</FieldLabel>
|
|
203
|
+
<Field name="address">
|
|
204
|
+
{({ field }: FieldProps) => (
|
|
205
|
+
<AddressAutocomplete
|
|
206
|
+
value={field.value || ""}
|
|
207
|
+
placeholder="Address, city, state, ZIP"
|
|
208
|
+
onChange={address => {
|
|
209
|
+
setFieldValue("address", address, false);
|
|
210
|
+
setFieldValue("isManualAddress", true, false);
|
|
211
|
+
void findCandidates(address, values);
|
|
212
|
+
}}
|
|
213
|
+
onSelectAddress={result => {
|
|
214
|
+
if (!result) return;
|
|
215
|
+
const units = (result.addressToSend.units ?? []).filter(
|
|
216
|
+
unit => unit.addressLine2
|
|
217
|
+
);
|
|
218
|
+
setFieldValue("address", result.address);
|
|
219
|
+
setFieldValue("addressToSend", result.addressToSend);
|
|
220
|
+
setFieldValue("unit", result.unit ?? "", false);
|
|
221
|
+
setFieldValue("isManualAddress", false, false);
|
|
222
|
+
setFieldValue("hasUnits", units.length > 0, false);
|
|
223
|
+
setUnitOptions([
|
|
224
|
+
...units.map(unit => ({
|
|
225
|
+
label: unit.addressLine2,
|
|
226
|
+
value: unit.dfAddressId ?? "",
|
|
227
|
+
data: unit,
|
|
228
|
+
})),
|
|
229
|
+
...(units.length > 0
|
|
230
|
+
? [
|
|
231
|
+
{
|
|
232
|
+
label: "I don't see my unit",
|
|
233
|
+
value: "manual",
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
label: "I don't have a unit",
|
|
237
|
+
value: "no-unit",
|
|
238
|
+
},
|
|
239
|
+
]
|
|
240
|
+
: []),
|
|
241
|
+
]);
|
|
242
|
+
}}
|
|
243
|
+
onKeyDown={event => {
|
|
244
|
+
if (event.key === "Enter") event.preventDefault();
|
|
245
|
+
}}
|
|
246
|
+
/>
|
|
247
|
+
)}
|
|
248
|
+
</Field>
|
|
249
|
+
<ErrorMessage
|
|
250
|
+
name="address"
|
|
251
|
+
errors={errors}
|
|
252
|
+
touched={touched}
|
|
253
|
+
/>
|
|
254
|
+
{showCandidates && (
|
|
255
|
+
<ul className="shadow-dark-drop mt-2 max-h-56 overflow-auto rounded-lg border border-input-border bg-white">
|
|
256
|
+
{candidates.map((candidate, index) => (
|
|
257
|
+
<li
|
|
258
|
+
key={`${candidate.dfAddressId ?? index}-${candidate.addressLine1}`}
|
|
259
|
+
>
|
|
260
|
+
<button
|
|
261
|
+
type="button"
|
|
262
|
+
className="flex w-full items-center gap-2 px-4 py-3 text-left hover:bg-bg-surface-hover"
|
|
263
|
+
onClick={() =>
|
|
264
|
+
selectCandidate(candidate, setFieldValue)
|
|
265
|
+
}
|
|
266
|
+
>
|
|
267
|
+
<MaterialIcon
|
|
268
|
+
name="location_on"
|
|
269
|
+
size={24}
|
|
270
|
+
fill={1}
|
|
271
|
+
className="text-green-600"
|
|
272
|
+
/>
|
|
273
|
+
{[
|
|
274
|
+
candidate.addressLine1,
|
|
275
|
+
candidate.city,
|
|
276
|
+
candidate.state,
|
|
277
|
+
candidate.postalCode,
|
|
278
|
+
]
|
|
279
|
+
.filter(Boolean)
|
|
280
|
+
.join(", ")}
|
|
281
|
+
</button>
|
|
282
|
+
</li>
|
|
283
|
+
))}
|
|
284
|
+
</ul>
|
|
285
|
+
)}
|
|
286
|
+
</div>
|
|
287
|
+
<div className="w-full sm:w-[216px] sm:flex-none">
|
|
288
|
+
<div className="mb-1 hidden h-[20px] sm:block" />
|
|
289
|
+
<Select
|
|
290
|
+
options={unitOptions}
|
|
291
|
+
value={selectedUnit}
|
|
292
|
+
placeholder="Apt, Suite, etc."
|
|
293
|
+
maxMenuHeight={200}
|
|
294
|
+
components={{
|
|
295
|
+
DropdownIndicator: () => (
|
|
296
|
+
<svg
|
|
297
|
+
width="20"
|
|
298
|
+
height="20"
|
|
299
|
+
viewBox="0 0 20 20"
|
|
300
|
+
fill="none"
|
|
301
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
302
|
+
aria-hidden="true"
|
|
303
|
+
>
|
|
304
|
+
<path
|
|
305
|
+
d="M5 7.5L10 12.5L15 7.5"
|
|
306
|
+
stroke="#1B2A4E"
|
|
307
|
+
strokeWidth="1.5"
|
|
308
|
+
strokeLinecap="round"
|
|
309
|
+
strokeLinejoin="round"
|
|
310
|
+
/>
|
|
311
|
+
</svg>
|
|
312
|
+
),
|
|
313
|
+
}}
|
|
314
|
+
styles={{
|
|
315
|
+
option: base => ({ ...base, ...placeholderBody2Style }),
|
|
316
|
+
singleValue: base => ({
|
|
317
|
+
...base,
|
|
318
|
+
...placeholderBody2Style,
|
|
319
|
+
}),
|
|
320
|
+
placeholder: base => ({
|
|
321
|
+
...base,
|
|
322
|
+
...placeholderBody2Style,
|
|
323
|
+
}),
|
|
324
|
+
input: base => ({ ...base, ...placeholderBody2Style }),
|
|
325
|
+
indicatorsContainer: base => ({
|
|
326
|
+
...base,
|
|
327
|
+
alignItems: "center",
|
|
328
|
+
}),
|
|
329
|
+
}}
|
|
330
|
+
className="w-full sm:w-[216px]"
|
|
331
|
+
controlClassName="!h-[56px] !min-h-[56px] !gap-2 !rounded-[12px] !border !border-solid !border-[#CECECE] !bg-white !pl-4 !pr-4 !flex-nowrap [&>div:first-child]:!overflow-visible [&>div:first-child>div]:!max-w-none [&>div:first-child>div]:!overflow-visible [&>div:first-child>div]:!whitespace-nowrap"
|
|
332
|
+
onChange={option => {
|
|
333
|
+
const selected = option as SelectOption | null;
|
|
334
|
+
if (!selected) return;
|
|
335
|
+
if (selected.value === "manual") {
|
|
336
|
+
setShowManualUnit(true);
|
|
337
|
+
setFieldValue("hasUnits", true, false);
|
|
338
|
+
setFieldValue("unit", "", false);
|
|
339
|
+
setFieldValue("addressToSend", {
|
|
340
|
+
...values.addressToSend,
|
|
341
|
+
addressLine2: "",
|
|
342
|
+
dfAddressId: undefined,
|
|
343
|
+
} as FiberAddress);
|
|
344
|
+
setSelectedUnit(selected);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (selected.value === "no-unit") {
|
|
348
|
+
setShowManualUnit(false);
|
|
349
|
+
setFieldValue("unit", "", false);
|
|
350
|
+
setFieldValue("hasUnits", false, false);
|
|
351
|
+
setFieldValue("addressToSend", {
|
|
352
|
+
...values.addressToSend,
|
|
353
|
+
addressLine2: "",
|
|
354
|
+
dfAddressId: undefined,
|
|
355
|
+
} as FiberAddress);
|
|
356
|
+
setSelectedUnit(selected);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
setShowManualUnit(false);
|
|
360
|
+
setSelectedUnit(selected);
|
|
361
|
+
setFieldValue("unit", selected.label);
|
|
362
|
+
setFieldValue("hasUnits", false, false);
|
|
363
|
+
setFieldValue("addressToSend", {
|
|
364
|
+
...values.addressToSend,
|
|
365
|
+
addressLine2: selected.label,
|
|
366
|
+
dfAddressId: selected.value,
|
|
367
|
+
} as FiberAddress);
|
|
368
|
+
}}
|
|
369
|
+
/>
|
|
370
|
+
{!showManualUnit &&
|
|
371
|
+
submitCount > 0 &&
|
|
372
|
+
(values.hasUnits ?? false) &&
|
|
373
|
+
!selectedUnit &&
|
|
374
|
+
typeof errors.unit === "string" && (
|
|
375
|
+
<span className="mt-1 block text-sm font-medium text-red-500">
|
|
376
|
+
{errors.unit}
|
|
377
|
+
</span>
|
|
378
|
+
)}
|
|
379
|
+
{showManualUnit && (
|
|
380
|
+
<div className="mt-5 w-full sm:w-[216px]">
|
|
381
|
+
<label className="mb-1 block text-sm font-medium text-neutral-900">
|
|
382
|
+
Unit<span className="text-red-500">*</span>
|
|
383
|
+
</label>
|
|
384
|
+
<Field
|
|
385
|
+
as={FormInput}
|
|
386
|
+
style={placeholderBody2Style}
|
|
387
|
+
name="unit"
|
|
388
|
+
placeholder="Please enter your unit"
|
|
389
|
+
/>
|
|
390
|
+
{submitCount > 0 && typeof errors.unit === "string" && (
|
|
391
|
+
<span className="mt-1 block text-sm font-medium text-red-500">
|
|
392
|
+
{errors.unit}
|
|
393
|
+
</span>
|
|
394
|
+
)}
|
|
395
|
+
</div>
|
|
396
|
+
)}
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
)}
|
|
400
|
+
|
|
401
|
+
<div className="mt-8 flex flex-col gap-5 sm:flex-row">
|
|
402
|
+
{inputList.phoneNumber && (
|
|
403
|
+
<div className="flex-1">
|
|
404
|
+
<FieldLabel required>Phone number</FieldLabel>
|
|
405
|
+
<Field name="phone">
|
|
406
|
+
{({ field, form }: FieldProps) => (
|
|
407
|
+
<FormInput
|
|
408
|
+
{...field}
|
|
409
|
+
type="tel"
|
|
410
|
+
style={placeholderBody2Style}
|
|
411
|
+
placeholder="(555) 555-5555"
|
|
412
|
+
onChange={event =>
|
|
413
|
+
form.setFieldValue(
|
|
414
|
+
"phone",
|
|
415
|
+
formatPhone(event.currentTarget.value)
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
/>
|
|
419
|
+
)}
|
|
420
|
+
</Field>
|
|
421
|
+
<ErrorMessage name="phone" errors={errors} touched={touched} />
|
|
422
|
+
</div>
|
|
423
|
+
)}
|
|
424
|
+
{inputList.emailAddress && (
|
|
425
|
+
<div className="flex-1">
|
|
426
|
+
<FieldLabel required>Email address</FieldLabel>
|
|
427
|
+
<Field
|
|
428
|
+
as={FormInput}
|
|
429
|
+
style={placeholderBody2Style}
|
|
430
|
+
name="email"
|
|
431
|
+
type="email"
|
|
432
|
+
placeholder="john.doe@example.com"
|
|
433
|
+
/>
|
|
434
|
+
<ErrorMessage name="email" errors={errors} touched={touched} />
|
|
435
|
+
</div>
|
|
436
|
+
)}
|
|
437
|
+
</div>
|
|
438
|
+
|
|
439
|
+
<div className="mt-8 flex flex-col gap-5 sm:flex-row">
|
|
440
|
+
{inputList.referralCode && (
|
|
441
|
+
<div className="flex-1">
|
|
442
|
+
<FieldLabel>Referral code</FieldLabel>
|
|
443
|
+
<Field
|
|
444
|
+
as={FormInput}
|
|
445
|
+
style={placeholderBody2Style}
|
|
446
|
+
name="referralCode"
|
|
447
|
+
placeholder="Optional"
|
|
448
|
+
/>
|
|
449
|
+
<ErrorMessage
|
|
450
|
+
name="referralCode"
|
|
451
|
+
errors={errors}
|
|
452
|
+
touched={touched}
|
|
453
|
+
/>
|
|
454
|
+
</div>
|
|
455
|
+
)}
|
|
456
|
+
{inputList.notes && (
|
|
457
|
+
<div className="flex-1">
|
|
458
|
+
<FieldLabel>Notes or comments</FieldLabel>
|
|
459
|
+
<Field
|
|
460
|
+
as={FormInput}
|
|
461
|
+
style={placeholderBody2Style}
|
|
462
|
+
name="notes"
|
|
463
|
+
placeholder="Optional"
|
|
464
|
+
/>
|
|
465
|
+
</div>
|
|
466
|
+
)}
|
|
467
|
+
</div>
|
|
468
|
+
|
|
469
|
+
{checkBoxEnabled && !hideLegalCopy && (
|
|
470
|
+
<div className="mt-16 text-neutral-600">
|
|
471
|
+
<Field name="isMarketingCom">
|
|
472
|
+
{({ field }: FieldProps) => (
|
|
473
|
+
<div className="mb-7 mt-4 flex items-center gap-1 pl-1">
|
|
474
|
+
<input
|
|
475
|
+
type="checkbox"
|
|
476
|
+
className="h-4 w-4 cursor-pointer align-middle accent-[#1d9d77]"
|
|
477
|
+
{...field}
|
|
478
|
+
checked={field.value}
|
|
479
|
+
id="recieve-message-checkbox"
|
|
480
|
+
/>
|
|
481
|
+
<label
|
|
482
|
+
htmlFor="recieve-message-checkbox"
|
|
483
|
+
className="mx-1 mb-3 mr-0 cursor-pointer align-middle text-micro font-medium leading-6 text-neutral-600"
|
|
484
|
+
>
|
|
485
|
+
Receive text messages for installation, account updates,
|
|
486
|
+
and marketing communications.
|
|
487
|
+
</label>
|
|
488
|
+
<Tooltip
|
|
489
|
+
tooltipMsg="By opting in to receive text messages, you will receive automated text message related to service notifications including installations, service changes and updates, billing information, marketing offers from Kinetic including cart reminders. Consent is not a condition of purchase. Reply STOP at any time to unsubscribe. Message frequency varies. Msg & data rates may apply. To contact our Customer Support via email, chat, or phone, visit our Support page below."
|
|
490
|
+
className="ff-NR mt-0"
|
|
491
|
+
>
|
|
492
|
+
<MaterialIcon
|
|
493
|
+
name="info"
|
|
494
|
+
size={20}
|
|
495
|
+
className="tooltip"
|
|
496
|
+
style={{ verticalAlign: "top" }}
|
|
497
|
+
/>
|
|
498
|
+
</Tooltip>
|
|
499
|
+
</div>
|
|
500
|
+
)}
|
|
501
|
+
</Field>
|
|
502
|
+
</div>
|
|
503
|
+
)}
|
|
504
|
+
{!hideLegalCopy && (
|
|
505
|
+
<Text className="body3 my-[32px] text-neutral-600 lg:mb-[64px] lg:mt-[32px]">
|
|
506
|
+
By submitting this form you consent to receive marketing messages
|
|
507
|
+
from Kinetic by text, email or phone to the contact provided.
|
|
508
|
+
Consent is not a condition of purchase. Msg & data rates may
|
|
509
|
+
apply. Msg frequency varies. Unsubscribe at any time by replying
|
|
510
|
+
STOP or clicking the unsubscribe link.
|
|
511
|
+
</Text>
|
|
512
|
+
)}
|
|
513
|
+
{bottomTextDisclaimer && !hideLegalCopy && (
|
|
514
|
+
<Text className="mt-2 text-center text-text-secondary">
|
|
515
|
+
{bottomTextDisclaimer}
|
|
516
|
+
</Text>
|
|
517
|
+
)}
|
|
518
|
+
{copyRightLinks && !hideLegalCopy && (
|
|
519
|
+
<ul className="mt-16 flex list-disc justify-center gap-8 text-text-secondary">
|
|
520
|
+
{copyRightLinks.map(link => (
|
|
521
|
+
<li key={link.title}>
|
|
522
|
+
<a
|
|
523
|
+
href={link.href}
|
|
524
|
+
target="_blank"
|
|
525
|
+
rel="noreferrer"
|
|
526
|
+
className="underline"
|
|
527
|
+
>
|
|
528
|
+
{link.title}
|
|
529
|
+
</a>
|
|
530
|
+
</li>
|
|
531
|
+
))}
|
|
532
|
+
</ul>
|
|
533
|
+
)}
|
|
534
|
+
<div className="mt-8 flex items-center justify-center px-[60px] lg:mt-[64px]">
|
|
535
|
+
<Button
|
|
536
|
+
type="submit"
|
|
537
|
+
disabled={isSubmitting}
|
|
538
|
+
className={cx(
|
|
539
|
+
"min-h-14 rounded-xl bg-bg-fill-brand px-8 py-4 font-bold text-white disabled:opacity-50",
|
|
540
|
+
buttonClassName
|
|
541
|
+
)}
|
|
542
|
+
>
|
|
543
|
+
{isSubmitting ? "Submitting..." : buttonText}
|
|
544
|
+
</Button>
|
|
545
|
+
</div>
|
|
546
|
+
</Form>
|
|
547
|
+
)}
|
|
548
|
+
</Formik>
|
|
549
|
+
);
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
export {
|
|
553
|
+
fiberFormAddressCollectionFormSchema,
|
|
554
|
+
fiberFormInputInitialValues,
|
|
555
|
+
fiberFormInputList,
|
|
556
|
+
} from "./constants";
|
|
557
|
+
export type * from "./types";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ComponentType, KeyboardEvent, ReactNode } from "react";
|
|
2
|
+
import type { FormikErrors, FormikTouched, FormikValues } from "formik";
|
|
3
|
+
import type * as Yup from "yup";
|
|
4
|
+
|
|
5
|
+
export type FiberAddressUnit = {
|
|
6
|
+
addressLine2: string;
|
|
7
|
+
dfAddressId?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type FiberAddress = {
|
|
11
|
+
addressLine1?: string;
|
|
12
|
+
addressLine2?: string | null;
|
|
13
|
+
city?: string;
|
|
14
|
+
state?: string;
|
|
15
|
+
postalCode?: string;
|
|
16
|
+
dfAddressId?: string;
|
|
17
|
+
units?: FiberAddressUnit[];
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type FiberAddressAutocompleteResult = {
|
|
22
|
+
address: string;
|
|
23
|
+
unit?: string;
|
|
24
|
+
addressToSend: FiberAddress;
|
|
25
|
+
placeId?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type FiberAddressAutocompleteProps = {
|
|
29
|
+
value: string;
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
errorText?: string;
|
|
32
|
+
onChange: (address: string) => void;
|
|
33
|
+
onSelectAddress: (result?: FiberAddressAutocompleteResult) => void;
|
|
34
|
+
onKeyDown: (event: KeyboardEvent<HTMLElement>) => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type FiberAddressCandidate = FiberAddress;
|
|
38
|
+
|
|
39
|
+
export type FiberFormValues = FormikValues & {
|
|
40
|
+
firstName: string;
|
|
41
|
+
lastName: string;
|
|
42
|
+
address: string;
|
|
43
|
+
phone: string;
|
|
44
|
+
email: string;
|
|
45
|
+
unit: string;
|
|
46
|
+
zip: string;
|
|
47
|
+
isManualAddress: boolean;
|
|
48
|
+
isMarketingCom: boolean;
|
|
49
|
+
addressToSend: FiberAddress;
|
|
50
|
+
notes: string;
|
|
51
|
+
referralCode: string;
|
|
52
|
+
hasUnits: boolean;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type FiberFormInputList = Partial<{
|
|
56
|
+
firstName: { placeholder?: string };
|
|
57
|
+
lastName: { placeholder?: string };
|
|
58
|
+
address: { placeholder?: string };
|
|
59
|
+
phoneNumber: { placeholder?: string };
|
|
60
|
+
emailAddress: { placeholder?: string };
|
|
61
|
+
unit: { placeholder?: string };
|
|
62
|
+
zip: { placeholder?: string };
|
|
63
|
+
notes: { placeholder?: string };
|
|
64
|
+
referralCode: { placeholder?: string };
|
|
65
|
+
}>;
|
|
66
|
+
|
|
67
|
+
export type FiberFormProps = {
|
|
68
|
+
InputComponent?: ComponentType<any>;
|
|
69
|
+
AddressAutocomplete: ComponentType<FiberAddressAutocompleteProps>;
|
|
70
|
+
initialValues?: FiberFormValues;
|
|
71
|
+
inputList?: FiberFormInputList;
|
|
72
|
+
formSchema?: Yup.AnyObjectSchema;
|
|
73
|
+
onSubmit: (values: FiberFormValues) => Promise<void>;
|
|
74
|
+
lookupAddress?: (
|
|
75
|
+
address: string,
|
|
76
|
+
values: FiberFormValues
|
|
77
|
+
) => Promise<FiberAddressCandidate[]>;
|
|
78
|
+
buttonText?: string;
|
|
79
|
+
buttonClassName?: string;
|
|
80
|
+
bottomTextDisclaimer?: ReactNode;
|
|
81
|
+
hideLegalCopy?: boolean;
|
|
82
|
+
checkBoxEnabled?: boolean;
|
|
83
|
+
copyRightLinks?: Array<{ title: string; href: string }>;
|
|
84
|
+
className?: string;
|
|
85
|
+
onAddressEvent?: (address: string) => void;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type FiberFormErrorProps = {
|
|
89
|
+
name: string;
|
|
90
|
+
errors: FormikErrors<FiberFormValues>;
|
|
91
|
+
touched: FormikTouched<FiberFormValues>;
|
|
92
|
+
};
|
|
@@ -364,7 +364,7 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
|
|
|
364
364
|
hasDesktopVideo && "h-[600px] w-[600px]"
|
|
365
365
|
)}
|
|
366
366
|
>
|
|
367
|
-
<Badge className="absolute -left-
|
|
367
|
+
<Badge className="absolute -left-10 top-18 z-10 aspect-square w-52 object-cover object-center" />
|
|
368
368
|
{!hasDesktopVideo && heroImageUrl ? (
|
|
369
369
|
<NextImage
|
|
370
370
|
src={heroImageUrl}
|