@axos-web-dev/shared-components 2.2.30 → 2.2.31

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.
@@ -73,8 +73,8 @@ import "../Carousel/index.js";
73
73
  /* empty css */
74
74
  import "../Chatbot/store/chat.js";
75
75
  import "../Chatbot/Chatbot.js";
76
- import "../Chatbot/Chatbot.css.ts.vanilla.css.js";
77
76
  import "../LoadingIndicator/LoadingIndicator.css.js";
77
+ import "../Chatbot/Chatbot.css.ts.vanilla.css.js";
78
78
  /* empty css */
79
79
  import "../Chatbot/store/messages.js";
80
80
  import { Hyperlink } from "../Hyperlink/index.js";
@@ -54,9 +54,9 @@ import "../Carousel/index.js";
54
54
  /* empty css */
55
55
  import "../Chatbot/store/chat.js";
56
56
  import "../Chatbot/Chatbot.js";
57
- import "../Chatbot/Chatbot.css.ts.vanilla.css.js";
58
57
  import "../LoadingIndicator/LoadingIndicator.css.js";
59
58
  import "react-use";
59
+ import "../Chatbot/Chatbot.css.ts.vanilla.css.js";
60
60
  /* empty css */
61
61
  import "../Chatbot/store/messages.js";
62
62
  /* empty css */
@@ -3,6 +3,7 @@ import '../assets/Chatbot/ChatWindow.css';import '../assets/Typography/Typograph
3
3
  /* empty css */
4
4
  /* empty css */
5
5
  /* empty css */
6
+ import "./Chatbot.css.ts.vanilla.css.js";
6
7
  /* empty css */
7
8
  var windowStyle = "_13n1jqk1";
8
9
  var windowExpandedStyle = "_13n1jqk2";
@@ -1,4 +1,3 @@
1
- import "./Chatbot.css.ts.vanilla.css.js";
2
1
  var chatbotAXB = "_1hpv6vm0";
3
2
  var chatbotUFB = "_1hpv6vm1";
4
3
  export {
@@ -22,7 +22,7 @@ import clsx from "clsx";
22
22
  import "../../Accordion/Accordion.js";
23
23
  import "../../Accordion/Accordion.css.js";
24
24
  import "../../AlertBanner/AlertBanner.css.js";
25
- import { useState, useEffect } from "react";
25
+ import { useState, useRef, useEffect, useLayoutEffect } from "react";
26
26
  import "../../Article/Article.css.js";
27
27
  import "../../ArticlesSet/ArticlesSet.css.js";
28
28
  import "../../Calculators/calculator.css.js";
@@ -134,6 +134,7 @@ import "../../StepItemSet/StepItemSet.css.js";
134
134
  /* empty css */
135
135
  /* empty css */
136
136
  /* empty css */
137
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
137
138
  const MortgageRate = ({
138
139
  icon = false,
139
140
  children,
@@ -159,6 +160,8 @@ const MortgageRate = ({
159
160
  const [_downPayment, setDownPayment] = useState(0);
160
161
  const [isTypingDownPayment, setIsTypingDownPayment] = useState(false);
161
162
  const [salesPriceVal, setSalesPriceVal] = useState(0);
163
+ const containerRef = useRef(null);
164
+ const restoreScrollOnNextRender = useRef(false);
162
165
  const cachedEmailValidator = useCachedEmailValidator(validateEmail);
163
166
  const schema = z.object({
164
167
  firstName: z.string({ message: "First Name is required." }).regex(/^[A-Za-z][^0-9_!¡?÷?¿/\\+=@#$%ˆ&*,.^(){}|~<>;:[\]]{1,}$/g, {
@@ -295,6 +298,17 @@ const MortgageRate = ({
295
298
  methods.setValue("downPayment", calculatedDownPayment);
296
299
  }
297
300
  }, [salesPrice, downPaymentPercentage]);
301
+ useIsomorphicLayoutEffect(() => {
302
+ if (!restoreScrollOnNextRender.current) return;
303
+ restoreScrollOnNextRender.current = false;
304
+ const anchorViewport = () => {
305
+ const target = showingTables && containerRef.current?.nextElementSibling || containerRef.current;
306
+ target?.scrollIntoView({ block: "start" });
307
+ };
308
+ anchorViewport();
309
+ const raf = requestAnimationFrame(anchorViewport);
310
+ return () => cancelAnimationFrame(raf);
311
+ }, [hideModal, showingTables]);
298
312
  const submitForm = async (data) => {
299
313
  const processData = {
300
314
  ...data
@@ -303,6 +317,7 @@ const MortgageRate = ({
303
317
  if (setRateWatchSubmitted) {
304
318
  setRateWatchSubmitted(false);
305
319
  }
320
+ restoreScrollOnNextRender.current = true;
306
321
  setHideModal(true);
307
322
  };
308
323
  const variant = getVariant(fullVariant);
@@ -366,7 +381,7 @@ const MortgageRate = ({
366
381
  }
367
382
  return value;
368
383
  };
369
- return /* @__PURE__ */ jsxs(Fragment, { children: [
384
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, children: [
370
385
  hideModal && !showingTables && /* @__PURE__ */ jsx(
371
386
  "section",
372
387
  {
@@ -442,9 +457,9 @@ const MortgageRate = ({
442
457
  {
443
458
  className: form,
444
459
  onSubmit: async (e) => {
460
+ e.preventDefault();
445
461
  onValidate && onValidate(e);
446
462
  await handleSubmit(submitForm)(e);
447
- e.preventDefault();
448
463
  },
449
464
  children: [
450
465
  /* @__PURE__ */ jsxs("div", { className: clsx(formWrapper({ variant })), children: [
@@ -794,12 +809,15 @@ const MortgageRate = ({
794
809
  {
795
810
  id,
796
811
  className: `${chevron({ variant })} ${!isValid ? na_cursor : ""}`,
797
- onClick: isValid ? handleSubmit(async (data) => {
798
- await onSubmit(data);
799
- toggleModal();
800
- setHideModal(false);
801
- }) : (e) => {
812
+ onClick: (e) => {
802
813
  e.preventDefault();
814
+ if (!isValid) return;
815
+ handleSubmit(async (data) => {
816
+ await onSubmit(data);
817
+ restoreScrollOnNextRender.current = true;
818
+ toggleModal();
819
+ setHideModal(false);
820
+ })(e);
803
821
  },
804
822
  children: "Sign Up For Ratewatch"
805
823
  }
@@ -36,7 +36,7 @@ const MortgageRateFilters = ({
36
36
  loanPurpose: z.number({ message: "Loan Purpose is required." }),
37
37
  propertyType: z.number({ message: "Property Type is required." }).positive({ message: "Property Type is required." }),
38
38
  occupancy: z.number({ message: "Property Usage is required." }).positive({ message: "Property Usage is required." }),
39
- salesPrice: z.number({ message: "Purchase Price is required." }).refine((val) => loanPurpose === 1 ? val != null : true, {
39
+ salesPrice: z.number({ message: "Purchase Price is required." }).optional().refine((val) => loanPurpose === 1 ? val != null : true, {
40
40
  message: "Sales Price is required."
41
41
  }).refine(
42
42
  (val) => {
@@ -47,7 +47,7 @@ const MortgageRateFilters = ({
47
47
  },
48
48
  { message: "Value must be between $50,000 and $25,000,000." }
49
49
  ),
50
- downPayment: z.number({ message: "Down Payment is required." }).refine((val) => loanPurpose === 1 ? val != null : true, {
50
+ downPayment: z.number({ message: "Down Payment is required." }).optional().refine((val) => loanPurpose === 1 ? val != null : true, {
51
51
  message: "Down Payment is required."
52
52
  }).refine(
53
53
  (val) => {
@@ -61,8 +61,8 @@ const MortgageRateFilters = ({
61
61
  }
62
62
  ),
63
63
  creditScore: z.number({ message: "Credit Score is required." }).positive({ message: "Credit Score is required." }),
64
- zipCode: z.number({ message: "Zip Code is required." }),
65
- appraisedValue: z.number({ message: "Home Price is required." }).refine(
64
+ zipCode: z.number({ message: "Zip Code is required." }).int({ message: "Zip Code must be a valid 5-digit zip code." }).gte(1e4, { message: "Zip Code must be a valid 5-digit zip code." }).lte(99999, { message: "Zip Code must be a valid 5-digit zip code." }),
65
+ appraisedValue: z.number({ message: "Home Price is required." }).optional().refine(
66
66
  (val) => {
67
67
  if (loanPurpose === 2) {
68
68
  return val != null && val >= 5e4 && val <= 25e6;
@@ -71,7 +71,7 @@ const MortgageRateFilters = ({
71
71
  },
72
72
  { message: "Value must be between $50,000 and $25,000,000." }
73
73
  ),
74
- mortgageBalance: z.number({ message: "Mortgage Balance is required." }).refine(
74
+ mortgageBalance: z.number({ message: "Mortgage Balance is required." }).optional().refine(
75
75
  (val) => {
76
76
  if (loanPurpose === 2) {
77
77
  return val != null && val >= 5e4 && val <= 25e6;
@@ -80,7 +80,7 @@ const MortgageRateFilters = ({
80
80
  },
81
81
  { message: "Value must be between $50,000 and $25,000,000." }
82
82
  ),
83
- cashOut: z.number({ message: "Cash Out is required." }).refine(
83
+ cashOut: z.number({ message: "Cash Out is required." }).optional().refine(
84
84
  (val) => {
85
85
  if (loanPurpose === 2 && val == null) {
86
86
  return false;
@@ -92,7 +92,19 @@ const MortgageRateFilters = ({
92
92
  });
93
93
  const methods = useForm({
94
94
  resolver: zodResolver(schema),
95
- mode: "all"
95
+ mode: "all",
96
+ defaultValues: {
97
+ loanPurpose: formData.loanPurpose,
98
+ propertyType: formData.propertyType,
99
+ occupancy: formData.occupancy,
100
+ salesPrice: formData.salesPrice,
101
+ downPayment: formData.downPayment,
102
+ creditScore: formData.creditScore,
103
+ zipCode: formData.zipCode,
104
+ appraisedValue: formData.appraisedValue,
105
+ mortgageBalance: formData.mortgageBalance,
106
+ cashOut: formData.cashOut
107
+ }
96
108
  });
97
109
  const {
98
110
  handleSubmit,
@@ -102,10 +114,10 @@ const MortgageRateFilters = ({
102
114
  } = methods;
103
115
  const salesPrice = watch("salesPrice");
104
116
  useEffect(() => {
105
- setSalesPriceVal(salesPrice);
117
+ setSalesPriceVal(salesPrice ?? 0);
106
118
  });
107
119
  useEffect(() => {
108
- if (salesPrice > 0 && !isTypingDownPayment) {
120
+ if (salesPrice != null && salesPrice > 0 && !isTypingDownPayment) {
109
121
  const calculatedDownPayment = Math.round(
110
122
  salesPrice * downPaymentPercentage / 100
111
123
  );
@@ -115,7 +127,16 @@ const MortgageRateFilters = ({
115
127
  }, [salesPrice, downPaymentPercentage]);
116
128
  const submitForm = async (data) => {
117
129
  const processData = {
118
- ...data
130
+ loanPurpose: data.loanPurpose,
131
+ propertyType: data.propertyType,
132
+ occupancy: data.occupancy,
133
+ creditScore: data.creditScore,
134
+ zipCode: data.zipCode,
135
+ salesPrice: data.salesPrice ?? 0,
136
+ downPayment: data.downPayment ?? 0,
137
+ appraisedValue: data.appraisedValue ?? 0,
138
+ mortgageBalance: data.mortgageBalance ?? 0,
139
+ cashOut: data.cashOut ?? 0
119
140
  };
120
141
  await onSubmit(processData);
121
142
  };
@@ -391,7 +412,8 @@ const MortgageRateFilters = ({
391
412
  inputMode: "numeric",
392
413
  ...register("zipCode", {
393
414
  required: true,
394
- pattern: /^\d{5}$/
415
+ pattern: /^\d{5}$/,
416
+ setValueAs: (value) => Number(value)
395
417
  }),
396
418
  label: "Zip Code",
397
419
  sizes: "medium",
@@ -1,6 +1,6 @@
1
1
  export type RateWatchInputs = {
2
2
  updateType: string;
3
- updateCadence: string;
3
+ updateCadence?: string;
4
4
  loanTerms?: string;
5
5
  hasSmsConsent: boolean;
6
6
  tR1DesiredRate?: string;
@@ -136,12 +136,34 @@ const RateWatchForm = ({
136
136
  toggleModal,
137
137
  setRateWatchSubmitted
138
138
  }) => {
139
+ const desiredRateFields = [
140
+ "tR1DesiredRate",
141
+ "tR2DesiredRate",
142
+ "tR3DesiredRate"
143
+ ];
144
+ const sanitizeDesiredRate = (value) => {
145
+ if (!value) return "";
146
+ const normalizedValue = value.replace(/[^\d.]/g, "");
147
+ if (!normalizedValue || normalizedValue === ".") return "";
148
+ const [wholePart, decimalPart = ""] = normalizedValue.split(".");
149
+ const trimmedDecimal = decimalPart.slice(0, 4);
150
+ const formattedValue = trimmedDecimal ? `${wholePart || "0"}.${trimmedDecimal}` : wholePart || "0";
151
+ const numericValue = Number(formattedValue);
152
+ if (!Number.isFinite(numericValue)) return "";
153
+ return Number.isInteger(numericValue) ? String(numericValue) : numericValue.toFixed(4).replace(/\.0+$|(?<=\.\d*)(0+)$/, "").replace(/\.$/, "");
154
+ };
155
+ const isDesiredRateOutOfRange = (value) => {
156
+ if (!value) return false;
157
+ const numericValue = Number(value);
158
+ if (!Number.isFinite(numericValue)) return false;
159
+ return numericValue < 2 || numericValue > 10;
160
+ };
139
161
  const [updateType, setUpdateType] = useState(
140
162
  "Target Rate"
141
163
  );
142
164
  const schema = z.object({
143
165
  updateType: z.string().min(1, { message: "Update type is required." }),
144
- updateCadence: z.string().refine((val) => !(updateType === "Frequency" && !val), {
166
+ updateCadence: z.string().optional().refine((val) => !(updateType === "Frequency" && !val), {
145
167
  message: "Frequency is required."
146
168
  }),
147
169
  loanTerms: z.string().optional().refine((val) => !(updateType === "Frequency" && !val), {
@@ -150,11 +172,13 @@ const RateWatchForm = ({
150
172
  hasSmsConsent: z.boolean().refine((val) => val === true, {
151
173
  message: "You must agree to receive SMS messages."
152
174
  }),
153
- tR1DesiredRate: z.string().optional().refine(
175
+ tR1DesiredRate: z.string().optional().refine((val) => updateType === "Target Rate" ? !!val : true, {
176
+ message: "Desired Rate is required."
177
+ }).refine(
154
178
  (val) => {
155
179
  if (val && updateType === "Target Rate") {
156
180
  const numericValue = +val;
157
- return !val || numericValue >= 2 && numericValue <= 10;
181
+ return numericValue >= 2 && numericValue <= 10;
158
182
  }
159
183
  return true;
160
184
  },
@@ -193,15 +217,21 @@ const RateWatchForm = ({
193
217
  email: z.string()
194
218
  });
195
219
  const methods = useForm({
196
- resolver: zodResolver(schema)
220
+ resolver: zodResolver(schema),
221
+ mode: "onChange"
197
222
  });
198
223
  const {
199
224
  handleSubmit,
200
225
  register,
201
226
  formState: { errors, isSubmitting, isValid },
202
227
  setValue,
203
- clearErrors
228
+ clearErrors,
229
+ watch
204
230
  } = methods;
231
+ const watchedDesiredRates = watch(desiredRateFields);
232
+ const hasDesiredRateWarning = desiredRateFields.some(
233
+ (_, index) => isDesiredRateOutOfRange(watchedDesiredRates[index])
234
+ );
205
235
  const submitForm = async (data) => {
206
236
  const processedData = {
207
237
  ...data,
@@ -240,12 +270,22 @@ const RateWatchForm = ({
240
270
  const handleTypeChange = (event) => {
241
271
  const value = event.target.value;
242
272
  setUpdateType(value);
273
+ methods.setValue("updateType", value, { shouldValidate: true });
274
+ };
275
+ const handleDesiredRateChange = (fieldName) => (event) => {
276
+ const sanitizedValue = sanitizeDesiredRate(event.target.value);
277
+ event.target.value = sanitizedValue;
278
+ setValue(fieldName, sanitizedValue, {
279
+ shouldValidate: true,
280
+ shouldDirty: true,
281
+ shouldTouch: true
282
+ });
243
283
  };
244
284
  const handleSubmitClick = () => {
245
285
  handleSubmit(submitForm)();
246
286
  };
247
287
  useEffect(() => {
248
- methods.reset();
288
+ methods.reset({ updateType });
249
289
  }, [updateType]);
250
290
  return /* @__PURE__ */ jsx("section", { className: "rate-watch-form", children: /* @__PURE__ */ jsx(FormProvider, { ...methods, children: /* @__PURE__ */ jsx(
251
291
  SuccesFormWrapper,
@@ -323,7 +363,7 @@ const RateWatchForm = ({
323
363
  /* @__PURE__ */ jsx("p", { className: mt1Rem, children: "You can establish up to three different targets with one rate watch." }),
324
364
  /* @__PURE__ */ jsxs("div", { className: threeColRow, children: [
325
365
  /* @__PURE__ */ jsxs("div", { children: [
326
- /* @__PURE__ */ jsx("label", { className: resposiveLabel, children: "Select a Loan Type*" }),
366
+ /* @__PURE__ */ jsx("label", { className: resposiveLabel, children: "Select a Loan Type" }),
327
367
  /* @__PURE__ */ jsxs(
328
368
  Dropdown,
329
369
  {
@@ -342,18 +382,24 @@ const RateWatchForm = ({
342
382
  )
343
383
  ] }),
344
384
  /* @__PURE__ */ jsxs("div", { children: [
345
- /* @__PURE__ */ jsx("label", { className: resposiveLabel, children: "Desired Rate" }),
385
+ /* @__PURE__ */ jsxs("label", { className: resposiveLabel, children: [
386
+ "Desired Rate",
387
+ /* @__PURE__ */ jsx("sup", { children: "*" })
388
+ ] }),
346
389
  /* @__PURE__ */ jsx(
347
390
  Input,
348
391
  {
349
392
  className: dynPH,
350
393
  ...register("tR1DesiredRate", {
351
- setValueAs: (value) => value ? `${value}` : ""
394
+ setValueAs: (value) => sanitizeDesiredRate(String(value ?? ""))
352
395
  }),
396
+ onChange: handleDesiredRateChange("tR1DesiredRate"),
353
397
  type: "number",
398
+ min: "2",
399
+ max: "10",
354
400
  placeholder: "Desired Rate",
355
401
  error: !!errors.tR1DesiredRate,
356
- helperText: errors.tR1DesiredRate?.message
402
+ helperText: isDesiredRateOutOfRange(watch("tR1DesiredRate")) ? "Desired Rate must be between 2 and 10." : errors.tR1DesiredRate?.message
357
403
  }
358
404
  )
359
405
  ] }),
@@ -404,9 +450,16 @@ const RateWatchForm = ({
404
450
  Input,
405
451
  {
406
452
  className: dynPH,
407
- ...register("tR2DesiredRate"),
408
- type: "string",
409
- placeholder: "Desired Rate"
453
+ ...register("tR2DesiredRate", {
454
+ setValueAs: (value) => sanitizeDesiredRate(String(value ?? ""))
455
+ }),
456
+ onChange: handleDesiredRateChange("tR2DesiredRate"),
457
+ type: "number",
458
+ min: "2",
459
+ max: "10",
460
+ placeholder: "Desired Rate",
461
+ error: isDesiredRateOutOfRange(watch("tR2DesiredRate")),
462
+ helperText: isDesiredRateOutOfRange(watch("tR2DesiredRate")) ? "Desired Rate must be between 2 and 10." : errors.tR2DesiredRate?.message
410
463
  }
411
464
  ) }),
412
465
  /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
@@ -452,10 +505,17 @@ const RateWatchForm = ({
452
505
  /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
453
506
  Input,
454
507
  {
455
- ...register("tR3DesiredRate"),
456
- type: "string",
508
+ ...register("tR3DesiredRate", {
509
+ setValueAs: (value) => sanitizeDesiredRate(String(value ?? ""))
510
+ }),
511
+ onChange: handleDesiredRateChange("tR3DesiredRate"),
512
+ type: "number",
513
+ min: "2",
514
+ max: "10",
457
515
  placeholder: "Desired Rate",
458
- className: dynPH
516
+ className: dynPH,
517
+ error: isDesiredRateOutOfRange(watch("tR3DesiredRate")),
518
+ helperText: isDesiredRateOutOfRange(watch("tR3DesiredRate")) ? "Desired Rate must be between 2 and 10." : errors.tR3DesiredRate?.message
459
519
  }
460
520
  ) }),
461
521
  /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
@@ -480,7 +540,20 @@ const RateWatchForm = ({
480
540
  }
481
541
  ) })
482
542
  ] }),
483
- /* @__PURE__ */ jsx("p", { className: mt1Rem, children: "*The number in Desired Rate must be at least 2% and no more than 10%" })
543
+ /* @__PURE__ */ jsxs(
544
+ "p",
545
+ {
546
+ className: mt1Rem,
547
+ style: {
548
+ color: hasDesiredRateWarning ? "#d92d20" : void 0,
549
+ fontWeight: hasDesiredRateWarning ? 600 : void 0
550
+ },
551
+ children: [
552
+ /* @__PURE__ */ jsx("sup", { children: "*" }),
553
+ " Please enter a desired rate between 2% and 10%"
554
+ ]
555
+ }
556
+ )
484
557
  ] }),
485
558
  /* @__PURE__ */ jsxs("div", { className: modalCheckboxContainer, children: [
486
559
  /* @__PURE__ */ jsx(
@@ -69,9 +69,9 @@ import "../Carousel/index.js";
69
69
  /* empty css */
70
70
  import "../Chatbot/store/chat.js";
71
71
  import "../Chatbot/Chatbot.js";
72
- import "../Chatbot/Chatbot.css.ts.vanilla.css.js";
73
72
  import "../LoadingIndicator/LoadingIndicator.css.js";
74
73
  import "react-use";
74
+ import "../Chatbot/Chatbot.css.ts.vanilla.css.js";
75
75
  /* empty css */
76
76
  import "../Chatbot/store/messages.js";
77
77
  /* empty css */
@@ -17,6 +17,7 @@ const DownPaymentInput = forwardRef(
17
17
  disabled,
18
18
  helperText: helper,
19
19
  error = false,
20
+ defaultValue,
20
21
  ...rest
21
22
  }, ref) => {
22
23
  const [downPayment, setDownPayment] = useState(0);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@axos-web-dev/shared-components",
3
3
  "description": "Axos shared components library for web.",
4
- "version": "2.2.30",
4
+ "version": "2.2.31",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
7
7
  "module": "dist/main.js",