@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windstream/react-shared-components",
3
- "version": "0.2.45",
3
+ "version": "0.2.47",
4
4
  "type": "module",
5
5
  "description": "Shared React components for Kinetic applications",
6
6
  "main": "dist/index.js",
@@ -110,12 +110,14 @@
110
110
  "@types/react-modal": "3.16.3",
111
111
  "@types/react-transition-group": "4.4.12",
112
112
  "clsx": "2.1.1",
113
+ "formik": "^2.4.6",
113
114
  "framer-motion": "10.12.16",
114
115
  "js-base64": "^3.7.7",
115
116
  "js-cookie": "^3.0.5",
116
117
  "react-modal": "3.16.3",
117
118
  "react-select": "5.10.2",
118
- "react-transition-group": "4.4.5"
119
+ "react-transition-group": "4.4.5",
120
+ "yup": "^1.7.0"
119
121
  },
120
122
  "optionalDependencies": {
121
123
  "@types/js-cookie": "^3.0.6"
@@ -48,8 +48,8 @@ export const BrandButton = forwardRef<
48
48
  const getVariantClasses = () => {
49
49
  const baseClasses = cx(
50
50
  sizeToClassNames(size),
51
- "rounded-button overflow-visible flex lg:inline-flex items-center justify-center text-center outline-none focus:ring-2 focus:ring-offset-2 cursor-pointer transition-colors duration-200 align-top lg:whitespace-nowrap",
52
- fullWidth ? "w-full" : "w-full lg:w-auto"
51
+ "rounded-button pl-15 pr-15 inline-flex gap-2 items-center justify-center outline-none focus:outline focus:outline-2 focus:outline-offset-2 cursor-pointer transition-colors duration-200 align-top sm:whitespace-nowrap",
52
+ fullWidth ? "w-full" : "w-auto"
53
53
  );
54
54
 
55
55
  const variantClasses: Record<ButtonVariantsT, string> = {
@@ -263,4 +263,4 @@ describe("VideoLink", () => {
263
263
  "false"
264
264
  );
265
265
  });
266
- });
266
+ });
@@ -20,10 +20,10 @@ export const VideoLink: React.FC<VideoLinkComponentProps> = ({
20
20
  const [isHovered, setIsHovered] = useState(false);
21
21
 
22
22
  const videoSourceType = videoLink?.videoSourceType;
23
+ const usingContentfulAsset =
24
+ videoSourceType === "contentful" && !!videoLink?.videoAssetUrl;
23
25
  const videoHref =
24
- (videoSourceType === "contentful"
25
- ? videoLink?.videoAssetUrl
26
- : videoLink?.link) ?? "";
26
+ (usingContentfulAsset ? videoLink?.videoAssetUrl : videoLink?.link) ?? "";
27
27
 
28
28
  // The video's own poster - never the promo bar's plain image field.
29
29
  const posterImage = videoLink?.image;
@@ -69,7 +69,7 @@ export const VideoLink: React.FC<VideoLinkComponentProps> = ({
69
69
  };
70
70
 
71
71
  const embedSelector = () => {
72
- if (videoSourceType === "contentful") {
72
+ if (usingContentfulAsset) {
73
73
  return (
74
74
  <ContentfulVideoEmbed
75
75
  videoAssetUrl={videoLink?.videoAssetUrl}
@@ -92,7 +92,7 @@ export const VideoLink: React.FC<VideoLinkComponentProps> = ({
92
92
  <>
93
93
  <div
94
94
  className={cx(
95
- "video-section relative w-full cursor-pointer rounded-image transition-all duration-300 lg:w-[486px]",
95
+ "video-section relative mx-auto w-full cursor-pointer rounded-image transition-all duration-300 lg:w-[486px]",
96
96
  !isPlaying && "hover:ring-2 hover:ring-border-focus"
97
97
  )}
98
98
  onClick={handlePlayClick}
@@ -167,4 +167,4 @@ export type {
167
167
  VideoEmbedProps,
168
168
  VideoLinkComponentProps,
169
169
  VideoLinkProps,
170
- } from "./types";
170
+ } from "./types";
@@ -1,204 +1,232 @@
1
+ /// <reference types="jest" />
2
+ /// <reference types="@testing-library/jest-dom" />
3
+
1
4
  import { EmailInputBlock } from "./index";
5
+ import { EmailInputBlockProps } from "./types";
2
6
 
3
7
  import { fireEvent, render, screen } from "@testing-library/react";
4
8
 
5
- describe("EmailInputBlock", () => {
6
- const defaultProps = {
7
- onSubmit: jest.fn(),
8
- };
9
+ const defaultProps: EmailInputBlockProps = {
10
+ onSubmit: jest.fn(),
11
+ };
9
12
 
13
+ describe("EmailInputBlock", () => {
10
14
  beforeEach(() => {
11
15
  jest.clearAllMocks();
12
16
  });
13
17
 
14
- it("renders with default props", () => {
15
- const { container } = render(<EmailInputBlock {...defaultProps} />);
16
- expect(container.querySelector("#email-input")).toBeInTheDocument();
17
- });
18
+ describe("Rendering", () => {
19
+ it("renders with default props", () => {
20
+ const { container } = render(<EmailInputBlock {...defaultProps} />);
21
+ expect(container.querySelector("#email-input")).toBeInTheDocument();
22
+ });
18
23
 
19
- it("renders custom anchorId", () => {
20
- const { container } = render(
21
- <EmailInputBlock {...defaultProps} anchorId="custom-anchor" />
22
- );
23
- expect(container.querySelector("#custom-anchor")).toBeInTheDocument();
24
- });
24
+ it("renders custom anchorId", () => {
25
+ const { container } = render(
26
+ <EmailInputBlock {...defaultProps} anchorId="custom-anchor" />
27
+ );
28
+ expect(container.querySelector("#custom-anchor")).toBeInTheDocument();
29
+ });
25
30
 
26
- it("renders title when provided", () => {
27
- render(<EmailInputBlock {...defaultProps} title="Join us" />);
28
- expect(screen.getByText("Join us")).toBeInTheDocument();
29
- expect(screen.getByText("Join us").tagName).toBe("H1");
30
- });
31
+ it("renders title when provided", () => {
32
+ render(<EmailInputBlock {...defaultProps} title="Join us" />);
33
+ const title = screen.getByText("Join us");
34
+ expect(title).toBeInTheDocument();
35
+ expect(title.tagName).toBe("H1");
36
+ });
31
37
 
32
- it("does not render title when not provided", () => {
33
- const { container } = render(<EmailInputBlock {...defaultProps} />);
34
- expect(container.querySelector("h1")).not.toBeInTheDocument();
35
- });
38
+ it("does not render title when not provided", () => {
39
+ const { container } = render(<EmailInputBlock {...defaultProps} />);
40
+ expect(container.querySelector("h1")).not.toBeInTheDocument();
41
+ });
36
42
 
37
- it("renders subTitle when provided", () => {
38
- render(<EmailInputBlock {...defaultProps} subTitle="Stay updated" />);
39
- expect(screen.getByText("Stay updated")).toBeInTheDocument();
40
- expect(screen.getByText("Stay updated").tagName).toBe("H2");
41
- });
43
+ it("renders subTitle when provided", () => {
44
+ render(<EmailInputBlock {...defaultProps} subTitle="Stay updated" />);
45
+ const subtitle = screen.getByText("Stay updated");
46
+ expect(subtitle).toBeInTheDocument();
47
+ expect(subtitle.tagName).toBe("H2");
48
+ });
42
49
 
43
- it("does not render subTitle when not provided", () => {
44
- const { container } = render(<EmailInputBlock {...defaultProps} />);
45
- expect(container.querySelector("h2")).not.toBeInTheDocument();
46
- });
50
+ it("does not render subTitle when not provided", () => {
51
+ const { container } = render(<EmailInputBlock {...defaultProps} />);
52
+ expect(container.querySelector("h2")).not.toBeInTheDocument();
53
+ });
47
54
 
48
- it("renders emailInputDescription when provided", () => {
49
- render(
50
- <EmailInputBlock {...defaultProps} emailInputDescription="Sign up now" />
51
- );
52
- expect(screen.getByText("Sign up now")).toBeInTheDocument();
53
- });
55
+ it("renders emailInputDescription when provided", () => {
56
+ render(
57
+ <EmailInputBlock
58
+ {...defaultProps}
59
+ emailInputDescription="Sign up now"
60
+ />
61
+ );
62
+ expect(screen.getByText("Sign up now")).toBeInTheDocument();
63
+ });
54
64
 
55
- it("renders caption when provided", () => {
56
- render(<EmailInputBlock {...defaultProps} caption="No spam" />);
57
- expect(screen.getByText("No spam")).toBeInTheDocument();
58
- });
65
+ it("renders caption when provided", () => {
66
+ render(<EmailInputBlock {...defaultProps} caption="No spam" />);
67
+ expect(screen.getByText("No spam")).toBeInTheDocument();
68
+ });
59
69
 
60
- it("does not render caption when not provided", () => {
61
- render(<EmailInputBlock {...defaultProps} />);
62
- expect(screen.queryByText("No spam")).not.toBeInTheDocument();
63
- });
70
+ it("does not render caption when not provided", () => {
71
+ render(<EmailInputBlock {...defaultProps} />);
72
+ expect(screen.queryByText("No spam")).not.toBeInTheDocument();
73
+ });
64
74
 
65
- it("uses default placeholder text", () => {
66
- render(<EmailInputBlock {...defaultProps} />);
67
- expect(
68
- screen.getByPlaceholderText("Enter your email here")
69
- ).toBeInTheDocument();
70
- });
75
+ it("uses default placeholder text", () => {
76
+ render(<EmailInputBlock {...defaultProps} />);
77
+ expect(
78
+ screen.getByPlaceholderText("Enter your email here")
79
+ ).toBeInTheDocument();
80
+ });
71
81
 
72
- it("uses custom placeholder text", () => {
73
- render(<EmailInputBlock {...defaultProps} inputPlaceholder="Your email" />);
74
- expect(screen.getByPlaceholderText("Your email")).toBeInTheDocument();
82
+ it("uses custom placeholder text", () => {
83
+ render(
84
+ <EmailInputBlock {...defaultProps} inputPlaceholder="Your email" />
85
+ );
86
+ expect(screen.getByPlaceholderText("Your email")).toBeInTheDocument();
87
+ });
75
88
  });
76
89
 
77
- it("calls onSubmit with email on valid submission", () => {
78
- render(<EmailInputBlock {...defaultProps} />);
79
- const input = screen.getByPlaceholderText("Enter your email here");
80
- fireEvent.change(input, { target: { value: "test@example.com" } });
81
- fireEvent.click(screen.getByText("Sign me up"));
82
- expect(defaultProps.onSubmit).toHaveBeenCalledWith({
83
- email: "test@example.com",
90
+ describe("Form submission", () => {
91
+ it("calls onSubmit with email on valid submission", () => {
92
+ render(<EmailInputBlock {...defaultProps} />);
93
+ const input = screen.getByPlaceholderText("Enter your email here");
94
+ fireEvent.change(input, { target: { value: "test@example.com" } });
95
+ fireEvent.click(screen.getByText("Sign me up"));
96
+ expect(defaultProps.onSubmit).toHaveBeenCalledWith({
97
+ email: "test@example.com",
98
+ });
84
99
  });
85
- });
86
100
 
87
- it("clears email after successful submission", () => {
88
- render(<EmailInputBlock {...defaultProps} />);
89
- const input = screen.getByPlaceholderText(
90
- "Enter your email here"
91
- ) as HTMLInputElement;
92
- fireEvent.change(input, { target: { value: "test@example.com" } });
93
- fireEvent.click(screen.getByText("Sign me up"));
94
- expect(input.value).toBe("");
101
+ it("clears email after successful submission", () => {
102
+ render(<EmailInputBlock {...defaultProps} />);
103
+ const input = screen.getByPlaceholderText(
104
+ "Enter your email here"
105
+ ) as HTMLInputElement;
106
+ fireEvent.change(input, { target: { value: "test@example.com" } });
107
+ fireEvent.click(screen.getByText("Sign me up"));
108
+ expect(input.value).toBe("");
109
+ });
95
110
  });
96
111
 
97
- it("shows error for invalid email", () => {
98
- render(<EmailInputBlock {...defaultProps} />);
99
- const input = screen.getByPlaceholderText("Enter your email here");
100
- fireEvent.change(input, { target: { value: "invalid" } });
101
- fireEvent.click(screen.getByText("Sign me up"));
102
- expect(screen.getByText("Invalid email address")).toBeInTheDocument();
103
- expect(defaultProps.onSubmit).not.toHaveBeenCalled();
104
- });
112
+ describe("Validation", () => {
113
+ it("shows error for invalid email", () => {
114
+ render(<EmailInputBlock {...defaultProps} />);
115
+ const input = screen.getByPlaceholderText("Enter your email here");
116
+ fireEvent.change(input, { target: { value: "invalid" } });
117
+ fireEvent.click(screen.getByText("Sign me up"));
118
+ expect(screen.getByText("Invalid email address")).toBeInTheDocument();
119
+ expect(defaultProps.onSubmit).not.toHaveBeenCalled();
120
+ });
105
121
 
106
- it("shows required error for empty email", () => {
107
- render(<EmailInputBlock {...defaultProps} />);
108
- fireEvent.click(screen.getByText("Sign me up"));
109
- expect(screen.getByText("Email is required")).toBeInTheDocument();
110
- expect(defaultProps.onSubmit).not.toHaveBeenCalled();
111
- });
122
+ it("shows required error for empty email", () => {
123
+ render(<EmailInputBlock {...defaultProps} />);
124
+ fireEvent.click(screen.getByText("Sign me up"));
125
+ expect(screen.getByText("Email is required")).toBeInTheDocument();
126
+ expect(defaultProps.onSubmit).not.toHaveBeenCalled();
127
+ });
112
128
 
113
- it("clears error when user types a valid email after error", () => {
114
- render(<EmailInputBlock {...defaultProps} />);
115
- const input = screen.getByPlaceholderText("Enter your email here");
116
- fireEvent.change(input, { target: { value: "invalid" } });
117
- fireEvent.click(screen.getByText("Sign me up"));
118
- expect(screen.getByText("Invalid email address")).toBeInTheDocument();
119
-
120
- fireEvent.change(input, { target: { value: "user@test.com" } });
121
- expect(
122
- screen.queryByText("Invalid email address")
123
- ).not.toBeInTheDocument();
129
+ it("clears error when user types a valid email after error", () => {
130
+ render(<EmailInputBlock {...defaultProps} />);
131
+ const input = screen.getByPlaceholderText("Enter your email here");
132
+ fireEvent.change(input, { target: { value: "invalid" } });
133
+ fireEvent.click(screen.getByText("Sign me up"));
134
+ expect(screen.getByText("Invalid email address")).toBeInTheDocument();
135
+
136
+ fireEvent.change(input, { target: { value: "user@test.com" } });
137
+ expect(
138
+ screen.queryByText("Invalid email address")
139
+ ).not.toBeInTheDocument();
140
+ });
124
141
  });
125
142
 
126
- it("shows success feedback after valid submission", () => {
127
- render(<EmailInputBlock {...defaultProps} successFeedback="Thanks!" />);
128
- const input = screen.getByPlaceholderText("Enter your email here");
129
- fireEvent.change(input, { target: { value: "user@test.com" } });
130
- fireEvent.click(screen.getByText("Sign me up"));
131
- expect(screen.getByText("Thanks!")).toBeInTheDocument();
132
- });
143
+ describe("Success feedback", () => {
144
+ it("shows success feedback after valid submission", () => {
145
+ render(<EmailInputBlock {...defaultProps} successFeedback="Thanks!" />);
146
+ const input = screen.getByPlaceholderText("Enter your email here");
147
+ fireEvent.change(input, { target: { value: "user@test.com" } });
148
+ fireEvent.click(screen.getByText("Sign me up"));
149
+ expect(screen.getByText("Thanks!")).toBeInTheDocument();
150
+ });
133
151
 
134
- it("hides success feedback when user types again", () => {
135
- render(<EmailInputBlock {...defaultProps} successFeedback="Thanks!" />);
136
- const input = screen.getByPlaceholderText("Enter your email here");
137
- fireEvent.change(input, { target: { value: "user@test.com" } });
138
- fireEvent.click(screen.getByText("Sign me up"));
139
- expect(screen.getByText("Thanks!")).toBeInTheDocument();
152
+ it("hides success feedback when user types again", () => {
153
+ render(<EmailInputBlock {...defaultProps} successFeedback="Thanks!" />);
154
+ const input = screen.getByPlaceholderText("Enter your email here");
155
+ fireEvent.change(input, { target: { value: "user@test.com" } });
156
+ fireEvent.click(screen.getByText("Sign me up"));
157
+ expect(screen.getByText("Thanks!")).toBeInTheDocument();
140
158
 
141
- fireEvent.change(input, { target: { value: "x" } });
142
- expect(screen.queryByText("Thanks!")).not.toBeInTheDocument();
143
- });
159
+ fireEvent.change(input, { target: { value: "x" } });
160
+ expect(screen.queryByText("Thanks!")).not.toBeInTheDocument();
161
+ });
144
162
 
145
- it("does not show success feedback without successFeedback prop", () => {
146
- render(<EmailInputBlock {...defaultProps} />);
147
- const input = screen.getByPlaceholderText("Enter your email here");
148
- fireEvent.change(input, { target: { value: "user@test.com" } });
149
- fireEvent.click(screen.getByText("Sign me up"));
150
- // No success text rendered
151
- expect(screen.queryByText("Thanks!")).not.toBeInTheDocument();
163
+ it("does not show success feedback without successFeedback prop", () => {
164
+ render(<EmailInputBlock {...defaultProps} />);
165
+ const input = screen.getByPlaceholderText("Enter your email here");
166
+ fireEvent.change(input, { target: { value: "user@test.com" } });
167
+ fireEvent.click(screen.getByText("Sign me up"));
168
+ expect(screen.queryByText("Thanks!")).not.toBeInTheDocument();
169
+ });
152
170
  });
153
171
 
154
- it("applies green theme background by default", () => {
155
- const { container } = render(<EmailInputBlock {...defaultProps} />);
156
- expect(container.querySelector(".bg-bg-fill-brand")).toBeInTheDocument();
157
- });
172
+ describe("Themes", () => {
173
+ it("applies green theme background by default", () => {
174
+ const { container } = render(<EmailInputBlock {...defaultProps} />);
175
+ expect(
176
+ container.querySelector(".bg-bg-fill-brand")
177
+ ).toBeInTheDocument();
178
+ });
158
179
 
159
- it("applies blue theme background class", () => {
160
- const { container } = render(
161
- <EmailInputBlock {...defaultProps} themeColor="blue" />
162
- );
163
- expect(
164
- container.querySelector(".bg-bg-fill-brand-supporting")
165
- ).toBeInTheDocument();
166
- });
180
+ it("applies blue theme background class", () => {
181
+ const { container } = render(
182
+ <EmailInputBlock {...defaultProps} themeColor="blue" />
183
+ );
184
+ expect(
185
+ container.querySelector(".bg-bg-fill-brand-supporting")
186
+ ).toBeInTheDocument();
187
+ });
167
188
 
168
- it("applies purple theme background class", () => {
169
- const { container } = render(
170
- <EmailInputBlock {...defaultProps} themeColor="purple" />
171
- );
172
- expect(
173
- container.querySelector(".bg-bg-fill-brand-tertiary")
174
- ).toBeInTheDocument();
175
- });
189
+ it("applies purple theme background class", () => {
190
+ const { container } = render(
191
+ <EmailInputBlock {...defaultProps} themeColor="purple" />
192
+ );
193
+ expect(
194
+ container.querySelector(".bg-bg-fill-brand-tertiary")
195
+ ).toBeInTheDocument();
196
+ });
176
197
 
177
- it("applies white outer background class by default", () => {
178
- const { container } = render(<EmailInputBlock {...defaultProps} />);
179
- expect(container.querySelector("section")).toHaveClass("bg-bg");
180
- });
198
+ it("applies white outer background class by default", () => {
199
+ const { container } = render(<EmailInputBlock {...defaultProps} />);
200
+ expect(container.querySelector("section")).toHaveClass("bg-bg");
201
+ });
181
202
 
182
- it("uses white text for dark themes", () => {
183
- render(
184
- <EmailInputBlock {...defaultProps} themeColor="blue" title="Blue title" />
185
- );
186
- expect(screen.getByText("Blue title").className).toContain("text-white");
187
- });
203
+ it("uses white text for dark themes", () => {
204
+ render(
205
+ <EmailInputBlock
206
+ {...defaultProps}
207
+ themeColor="blue"
208
+ title="Blue title"
209
+ />
210
+ );
211
+ expect(screen.getByText("Blue title")).toHaveClass("text-white");
212
+ });
188
213
 
189
- it("uses dark text for the white theme", () => {
190
- render(
191
- <EmailInputBlock
192
- {...defaultProps}
193
- themeColor="white"
194
- title="White title"
195
- />
196
- );
197
- expect(screen.getByText("White title").className).toContain("text-text");
214
+ it("uses dark text for the white theme", () => {
215
+ render(
216
+ <EmailInputBlock
217
+ {...defaultProps}
218
+ themeColor="white"
219
+ title="White title"
220
+ />
221
+ );
222
+ expect(screen.getByText("White title")).toHaveClass("text-text");
223
+ });
198
224
  });
199
225
 
200
- it("uses custom ctaText", () => {
201
- render(<EmailInputBlock {...defaultProps} ctaText="Subscribe" />);
202
- expect(screen.getByText("Subscribe")).toBeInTheDocument();
226
+ describe("Call to action", () => {
227
+ it("uses custom ctaText", () => {
228
+ render(<EmailInputBlock {...defaultProps} ctaText="Subscribe" />);
229
+ expect(screen.getByText("Subscribe")).toBeInTheDocument();
230
+ });
203
231
  });
204
232
  });
@@ -0,0 +1,81 @@
1
+ import type { FiberFormInputList, FiberFormValues } from "../../blocks/fiber-form/types";
2
+ import * as Yup from "yup";
3
+
4
+ export const fiberFormInputInitialValues: FiberFormValues = {
5
+ firstName: "",
6
+ lastName: "",
7
+ address: "",
8
+ phone: "",
9
+ email: "",
10
+ unit: "",
11
+ zip: "",
12
+ isManualAddress: false,
13
+ isMarketingCom: false,
14
+ addressToSend: {},
15
+ notes: "",
16
+ referralCode: "",
17
+ hasUnits: false,
18
+ };
19
+
20
+ export const fiberFormInputList: FiberFormInputList = {
21
+ firstName: { placeholder: "John" },
22
+ lastName: { placeholder: "Doe" },
23
+ address: { placeholder: "Street address" },
24
+ phoneNumber: { placeholder: "Phone number" },
25
+ emailAddress: { placeholder: "Email address" },
26
+ unit: { placeholder: "Unit (optional)" },
27
+ zip: { placeholder: "Zip" },
28
+ notes: { placeholder: "Notes or Comments" },
29
+ referralCode: { placeholder: "Referral Code" },
30
+ };
31
+
32
+ export const fiberFormAddressCollectionFormSchema = Yup.object({
33
+ firstName: Yup.string()
34
+ .required("First name is required")
35
+ .min(2, "Enter a valid First name")
36
+ .matches(/^[a-zA-Z\s]*$/, "Enter a valid First name"),
37
+ lastName: Yup.string()
38
+ .required("Last name is required")
39
+ .min(2, "Enter a valid Last name")
40
+ .matches(/^[a-zA-Z\s]*$/, "Enter a valid Last name"),
41
+ isManualAddress: Yup.boolean(),
42
+ isMarketingCom: Yup.boolean(),
43
+ hasUnits: Yup.boolean(),
44
+ unit: Yup.string().when("hasUnits", {
45
+ is: true,
46
+ then: schema => schema.required("Unit is required"),
47
+ otherwise: schema =>
48
+ schema.matches(/^[a-zA-Z0-9\s#]*$/, "Enter a valid Unit"),
49
+ }),
50
+ zip: Yup.string().when("isManualAddress", {
51
+ is: true,
52
+ then: schema =>
53
+ schema
54
+ .required("Zip is required")
55
+ .matches(/^\d{5}$/, "Enter a valid Zip"),
56
+ }),
57
+ address: Yup.string()
58
+ .required("Enter your home address (street, city, state, and ZIP below)")
59
+ .when("isManualAddress", {
60
+ is: true,
61
+ then: schema =>
62
+ schema.matches(
63
+ /^(\d{1,}) [a-zA-Z0-9\s]+(\,)? [a-zA-Z\s]+(\,)? [A-Z]{2}( [0-9]{5,6})?$/,
64
+ "Enter your home address (street, city, state, and ZIP below)"
65
+ ),
66
+ }),
67
+ phone: Yup.string()
68
+ .required("Phone is required")
69
+ .matches(
70
+ /^\([0-9]{3}\)\s[0-9]{3}-[0-9]{4}$/,
71
+ "Please enter a valid phone number"
72
+ ),
73
+ email: Yup.string()
74
+ .email("Invalid email")
75
+ .matches(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, "Invalid email")
76
+ .required("Email is required"),
77
+ notes: Yup.string(),
78
+ referralCode: Yup.string()
79
+ .max(50, "Referral code must not exceed 50 characters")
80
+ .matches(/^\S*$/, "Referral code must not contain spaces"),
81
+ });