@windstream/react-shared-components 0.2.57 → 0.2.59

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 (38) hide show
  1. package/dist/contentful/index.d.ts +15 -1
  2. package/dist/contentful/index.esm.js +5 -5
  3. package/dist/contentful/index.esm.js.map +1 -1
  4. package/dist/contentful/index.js +5 -5
  5. package/dist/contentful/index.js.map +1 -1
  6. package/dist/index.esm.js +1 -1
  7. package/dist/index.esm.js.map +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/next/index.esm.js +1 -17
  11. package/dist/next/index.esm.js.map +1 -1
  12. package/dist/next/index.js +1 -17
  13. package/dist/next/index.js.map +1 -1
  14. package/dist/styles.css +1 -1
  15. package/dist/utils/index.d.ts +11 -1
  16. package/dist/utils/index.esm.js +1 -1
  17. package/dist/utils/index.esm.js.map +1 -1
  18. package/dist/utils/index.js +1 -1
  19. package/dist/utils/index.js.map +1 -1
  20. package/package.json +1 -1
  21. package/src/components/brand-button/index.tsx +2 -2
  22. package/src/components/next-image/index.tsx +1 -1
  23. package/src/components/video-link/contentful-video-embed.tsx +1 -1
  24. package/src/components/video-link/index.tsx +1 -1
  25. package/src/components/video-link/vimeo-embed.tsx +1 -1
  26. package/src/components/video-link/youtube-embed.tsx +1 -1
  27. package/src/contentful/blocks/button/index.tsx +11 -0
  28. package/src/contentful/blocks/cart-retention-banner/CartRetentionBanner.stories.tsx +89 -1
  29. package/src/contentful/blocks/cart-retention-banner/index.test.tsx +85 -0
  30. package/src/contentful/blocks/cart-retention-banner/index.tsx +222 -39
  31. package/src/contentful/blocks/cart-retention-banner/types.ts +34 -0
  32. package/src/contentful/blocks/dynamic-form/form-field.tsx +1 -1
  33. package/src/contentful/blocks/modal/index.tsx +14 -3
  34. package/src/contentful/blocks/shape-background-wrapper/index.test.tsx +17 -1
  35. package/src/contentful/blocks/shape-background-wrapper/index.tsx +30 -0
  36. package/src/contentful/index.ts +5 -0
  37. package/src/utils/index.ts +5 -0
  38. package/src/utils/wifi-grant-redirect.ts +49 -0
@@ -59,7 +59,13 @@ export const Modal: React.FC<ExtendedModalProps> = props => {
59
59
  <Dialog.Content
60
60
  onOpenAutoFocus={e => e.preventDefault()}
61
61
  className={cx(
62
- "relative mx-auto w-full rounded-modal bg-bg p-[25px] shadow-drop focus:outline-none",
62
+ // `rounded-modal` isn't a registered Tailwind token - use an explicit radius.
63
+ "relative mx-auto w-full overflow-hidden rounded-[24px] shadow-drop focus:outline-none",
64
+ // Only fall back to the default surface color / padding when
65
+ // the caller hasn't supplied its own override (avoids two
66
+ // competing `bg-*`/`p-*` classes racing on CSS order).
67
+ !/(^|\s)bg-/.test(bodyClassName ?? "") && "bg-bg",
68
+ !/(^|\s)p-/.test(bodyClassName ?? "") && "p-[25px]",
63
69
  bodyClassName
64
70
  )}
65
71
  style={{ maxWidth: sizeToPixel[size || "lg"] }}
@@ -68,12 +74,17 @@ export const Modal: React.FC<ExtendedModalProps> = props => {
68
74
  <Dialog.Close asChild={true}>
69
75
  <Button
70
76
  showButtonAs="unstyled"
71
- buttonClassName="absolute right-0 top-0 mr-2 mt-2 bg-bg"
77
+ // Pin icon color: button always sits on a light bg-bg
78
+ // square, so it must not inherit a light text color (e.g.
79
+ // text-white) from a dark bodyClassName like navy.
80
+ // z-10: keeps the button clickable above positioned body
81
+ // content (e.g. ShapeBackgroundWrapper's decorative layer).
82
+ buttonClassName="absolute right-0 top-0 z-10 mr-2 mt-2"
72
83
  >
73
84
  <MaterialIcon name="close" />
74
85
  </Button>
75
86
  </Dialog.Close>
76
- <div className="custom-modal-body max-h-[85vh] overflow-visible">
87
+ <div className="custom-modal-body max-h-[85vh] overflow-y-auto scroll-smooth [scrollbar-color:rgba(0,0,0,0.2)_transparent] [scrollbar-width:thin] [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-black/20 hover:[&::-webkit-scrollbar-thumb]:bg-black/30 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar]:w-1.5">
77
88
  {title ? (
78
89
  <Text
79
90
  className={cx(
@@ -130,9 +130,25 @@ describe("ShapeBackgroundWrapper", () => {
130
130
  expect(svgs[0]).toHaveAttribute("height", "3666");
131
131
  });
132
132
 
133
+ it("renders both mobile and desktop path5 SVGs when path='path5'", () => {
134
+ const { container } = render(
135
+ <ShapeBackgroundWrapper path="path5" background="white">
136
+ <span>Path5</span>
137
+ </ShapeBackgroundWrapper>
138
+ );
139
+ const svgs = container.querySelectorAll("svg");
140
+ expect(svgs).toHaveLength(2);
141
+ expect(svgs[0]).toHaveAttribute("width", "367");
142
+ expect(svgs[0]).toHaveAttribute("height", "644");
143
+ expect(svgs[0]).toHaveClass("md:hidden");
144
+ expect(svgs[1]).toHaveAttribute("width", "367");
145
+ expect(svgs[1]).toHaveAttribute("height", "448");
146
+ expect(svgs[1]).toHaveClass("md:block");
147
+ });
148
+
133
149
  it("does not render SVG for unrecognized path value", () => {
134
150
  const { container } = render(
135
- <ShapeBackgroundWrapper path={"path5" as any} background="white">
151
+ <ShapeBackgroundWrapper path={"path6" as any} background="white">
136
152
  <span>Unknown</span>
137
153
  </ShapeBackgroundWrapper>
138
154
  );
@@ -118,6 +118,36 @@ export const ShapeBackgroundWrapper: React.FC<ShapeBackgroundWrapperProps> = ({
118
118
  <path d="M1662.93 3664.31C2153.52 3637.23 2442.1 3474.65 2872.31 2646.41C3278.25 1865.1 4009.1 647.091 3634.26 264.381C3417.79 27.9951 3025.37 31.7826 2716.76 13.902C2511.72 8.81709 2327.31 8.87474 2126.55 0.512528C2060.73 -2.00052 2040.7 85.9483 2101.3 114.088C2417.76 260.743 2628.86 431.51 2731.94 722.143C2940.79 1225.9 2508.91 2178.82 2150.63 2656.7C1779.64 3169.63 1293.64 3217.84 798.345 3136.04C534.128 3085.8 291.072 2986.98 92.1133 2864.4C33.4593 2828.06 -29.3236 2901.01 15.967 2953.13C371.841 3364.12 1128.29 3656.09 1663.56 3665.34" />
119
119
  </svg>
120
120
  )}
121
+ {path === "path5" && (
122
+ // Exact Figma export, cropped via viewBox at 0,0 (no translate hack needed);
123
+ // mobile/desktop swap via CSS breakpoint since the crop window differs by height.
124
+ <>
125
+ <svg
126
+ className={`shape-bg-svg pointer-events-none absolute left-0 top-0 z-0 block md:hidden ${variantClass} fill-current ${fillClass}`}
127
+ width="367"
128
+ height="644"
129
+ viewBox="0 0 367 644"
130
+ fill="none"
131
+ xmlns="http://www.w3.org/2000/svg"
132
+ aria-hidden="true"
133
+ focusable="false"
134
+ >
135
+ <path d="M-2083.1 549.586C-1857.78 764.836 -1656.5 828.759 -1081.83 673.557C-539.683 527.203 347.968 344.365 364.89 7.43135C381.862 -193.724 210.008 -373.544 84.6305 -523.879C-1.85227 -620.854 -81.806 -706.094 -164.9 -802.494C-192.25 -834.002 -242.154 -805.661 -229.077 -765.61C-160.659 -556.59 -149.205 -385.975 -240.751 -214.056C-386.348 97.8893 -1020.17 305.621 -1399.44 344.278C-1800.65 392.049 -2033.89 187.954 -2210.22 -76.0251C-2301.18 -219.67 -2360.21 -374.3 -2388.99 -518.696C-2397.38 -561.352 -2458.78 -559.193 -2463.58 -515.969C-2501.97 -175.709 -2310.97 298.878 -2083.31 550.316" />
136
+ </svg>
137
+ <svg
138
+ className={`shape-bg-svg pointer-events-none absolute left-0 top-0 z-0 hidden md:block ${variantClass} fill-current ${fillClass}`}
139
+ width="367"
140
+ height="448"
141
+ viewBox="0 0 367 448"
142
+ fill="none"
143
+ xmlns="http://www.w3.org/2000/svg"
144
+ aria-hidden="true"
145
+ focusable="false"
146
+ >
147
+ <path d="M-2083.1 549.587C-1857.78 764.837 -1656.5 828.759 -1081.83 673.557C-539.682 527.203 347.968 344.365 364.89 7.43156C381.863 -193.724 210.008 -373.543 84.6307 -523.879C-1.85203 -620.854 -81.8058 -706.094 -164.9 -802.494C-192.249 -834.001 -242.154 -805.661 -229.077 -765.609C-160.658 -556.59 -149.205 -385.975 -240.751 -214.056C-386.348 97.8895 -1020.17 305.621 -1399.44 344.278C-1800.65 392.049 -2033.89 187.954 -2210.22 -76.0249C-2301.18 -219.669 -2360.21 -374.3 -2388.99 -518.696C-2397.38 -561.352 -2458.78 -559.193 -2463.58 -515.969C-2501.97 -175.709 -2310.97 298.879 -2083.31 550.317" />
148
+ </svg>
149
+ </>
150
+ )}
121
151
 
122
152
  <div className="shape-bg-content relative z-[1]">{children}</div>
123
153
  </div>
@@ -98,6 +98,11 @@ export type { EmailInputBlockProps } from "./blocks/email-input-block/types";
98
98
 
99
99
  export { default as CartRetentionBanner } from "./blocks/cart-retention-banner";
100
100
  export type { TypeCartRetentionBannerFields as CartRetentionBannerProps } from "./blocks/cart-retention-banner/types";
101
+ export type {
102
+ CartRetentionBannerImagePosition,
103
+ CartRetentionBannerBackgroundColor,
104
+ CartRetentionBannerImageAccentColor,
105
+ } from "./blocks/cart-retention-banner/types";
101
106
 
102
107
  export { IframeEmbed } from "./blocks/iframe-embed";
103
108
  export type { IframeEmbedProps } from "./blocks/iframe-embed/types";
@@ -27,6 +27,11 @@ export type {
27
27
  } from "../types/utm";
28
28
  export { UTM_PARAM_NAMES } from "../types/utm";
29
29
 
30
+ export {
31
+ buildWifiGrantRedirectUrl,
32
+ redirectToWifiGrantUrl,
33
+ } from "./wifi-grant-redirect";
34
+
30
35
  // Contentful utilities (server-safe, no "use client")
31
36
  export { toDocument } from "./contentful/to-document";
32
37
  export {
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Wifi Grant Redirect Helpers
3
+ *
4
+ * Builds the captive-portal redirect URL for Wi-Fi splash pages (e.g. /free-public-wifi).
5
+ * The portal controller appends base_grant_url to the page URL; continue_url always
6
+ * points at the fixed success page below (not read from the portal's query params).
7
+ */
8
+
9
+ const WIFI_GRANT_CONTINUE_URL = "https://www.gokinetic.com/free-public-wifi/success";
10
+ const ALLOWED_BASE_GRANT_PROTOCOL = "https:";
11
+ // Cisco Meraki captive-portal domain; grant URLs use a per-node subdomain (e.g. n143.network-auth.com).
12
+ const ALLOWED_BASE_GRANT_DOMAIN = "network-auth.com";
13
+
14
+ // Restricts redirects to the known captive-portal domain to prevent open redirect via base_grant_url.
15
+ function getSafeBaseGrantUrl(rawBaseGrantUrl: string): URL | null {
16
+ try {
17
+ const parsed = new URL(rawBaseGrantUrl);
18
+ if (parsed.protocol !== ALLOWED_BASE_GRANT_PROTOCOL) return null;
19
+ const { hostname } = parsed;
20
+ const isAllowedHost =
21
+ hostname === ALLOWED_BASE_GRANT_DOMAIN ||
22
+ hostname.endsWith(`.${ALLOWED_BASE_GRANT_DOMAIN}`);
23
+ if (!isAllowedHost) return null;
24
+ return parsed;
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ export function buildWifiGrantRedirectUrl(): string | null {
31
+ if (typeof window === "undefined") return null;
32
+ const params = new URLSearchParams(window.location.search);
33
+ const baseGrantUrl = params.get("base_grant_url");
34
+ if (!baseGrantUrl) return null;
35
+
36
+ const safeBaseGrantUrl = getSafeBaseGrantUrl(baseGrantUrl);
37
+ if (!safeBaseGrantUrl) return null;
38
+
39
+ safeBaseGrantUrl.searchParams.set("continue_url", WIFI_GRANT_CONTINUE_URL);
40
+ safeBaseGrantUrl.searchParams.set("duration", "86400");
41
+ return safeBaseGrantUrl.toString();
42
+ }
43
+
44
+ export function redirectToWifiGrantUrl(): boolean {
45
+ const redirectUrl = buildWifiGrantRedirectUrl();
46
+ if (!redirectUrl) return false;
47
+ window.location.href = redirectUrl;
48
+ return true;
49
+ }