@windstream/react-shared-components 0.2.58 → 0.2.60
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 -0
- package/dist/contentful/index.esm.js +4 -4
- package/dist/contentful/index.esm.js.map +1 -1
- package/dist/contentful/index.js +4 -4
- package/dist/contentful/index.js.map +1 -1
- package/dist/utils/index.d.ts +11 -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 +1 -1
- package/src/contentful/blocks/award-marquee/index.test.tsx +20 -0
- package/src/contentful/blocks/award-marquee/index.tsx +15 -2
- package/src/contentful/blocks/award-marquee/types.ts +2 -0
- package/src/contentful/blocks/button/index.tsx +10 -0
- package/src/contentful/blocks/dynamic-form/form-field.tsx +1 -1
- package/src/utils/index.ts +5 -0
- package/src/utils/wifi-grant-redirect.ts +49 -0
package/package.json
CHANGED
|
@@ -28,6 +28,26 @@ describe("AwardMarquee", () => {
|
|
|
28
28
|
expect(icon.src).toContain("icon-0.png");
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
it("renders intrinsic icon dimensions when provided", () => {
|
|
32
|
+
const [item] = buildItems(1);
|
|
33
|
+
render(
|
|
34
|
+
<AwardMarquee
|
|
35
|
+
items={[{ ...item, iconWidth: 320, iconHeight: 180 }]}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
const icon = screen.getByAltText("Award 0");
|
|
39
|
+
expect(icon).toHaveAttribute("width", "320");
|
|
40
|
+
expect(icon).toHaveAttribute("height", "180");
|
|
41
|
+
expect(icon).toHaveClass("w-[200px]");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("omits intrinsic icon dimensions when unavailable", () => {
|
|
45
|
+
render(<AwardMarquee items={buildItems(1)} />);
|
|
46
|
+
const icon = screen.getByAltText("Award 0");
|
|
47
|
+
expect(icon).not.toHaveAttribute("width");
|
|
48
|
+
expect(icon).not.toHaveAttribute("height");
|
|
49
|
+
});
|
|
50
|
+
|
|
31
51
|
it("renders a static (non-animated) list when scroll is disabled", () => {
|
|
32
52
|
const { container } = render(
|
|
33
53
|
<AwardMarquee items={buildItems(5)} scroll={false} />
|
|
@@ -21,14 +21,27 @@ const BACKGROUND_CLASS: Record<AwardMarqueeBackground, string> = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
const renderAwardItem = (
|
|
24
|
-
{
|
|
24
|
+
{
|
|
25
|
+
iconUrl,
|
|
26
|
+
iconAlt,
|
|
27
|
+
iconWidth,
|
|
28
|
+
iconHeight,
|
|
29
|
+
title,
|
|
30
|
+
description,
|
|
31
|
+
}: AwardMarqueeItem,
|
|
25
32
|
key: React.Key
|
|
26
33
|
) => (
|
|
27
34
|
<div
|
|
28
35
|
key={key}
|
|
29
36
|
className="ml-4 flex w-auto flex-shrink-0 items-center whitespace-nowrap"
|
|
30
37
|
>
|
|
31
|
-
<img
|
|
38
|
+
<img
|
|
39
|
+
src={iconUrl}
|
|
40
|
+
alt={iconAlt ?? ""}
|
|
41
|
+
width={iconWidth}
|
|
42
|
+
height={iconHeight}
|
|
43
|
+
className="w-[200px]"
|
|
44
|
+
/>
|
|
32
45
|
<div className="w-[250px] text-center">
|
|
33
46
|
<p className="flex justify-center text-center text-body3 font-bold">
|
|
34
47
|
{title}
|
|
@@ -13,6 +13,7 @@ import type { IconName } from "@shared/components/material-icon/types";
|
|
|
13
13
|
import { Text } from "@shared/components/text";
|
|
14
14
|
import { cx } from "@shared/utils";
|
|
15
15
|
import { buildPreservedQueryHref } from "@shared/utils/utm";
|
|
16
|
+
import { redirectToWifiGrantUrl } from "@shared/utils/wifi-grant-redirect";
|
|
16
17
|
|
|
17
18
|
const textVariantClasses: Record<string, string> = {
|
|
18
19
|
primary_brand: "text-text-brand",
|
|
@@ -114,13 +115,22 @@ export const Button: React.FC<ButtonProps> = props => {
|
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
// Self-contained: reads base_grant_url/user_continue_url and redirects, no host wiring needed.
|
|
119
|
+
function handleWifiGrantRedirect() {
|
|
120
|
+
if (preDefinedFunctionExecution === "wifi grant redirect") {
|
|
121
|
+
redirectToWifiGrantUrl();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
117
125
|
function linkClick(event: React.MouseEvent<HTMLAnchorElement>) {
|
|
118
126
|
handleModalClick(event);
|
|
127
|
+
handleWifiGrantRedirect();
|
|
119
128
|
onClick?.(event);
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
function buttonClick(event: React.MouseEvent<HTMLButtonElement>) {
|
|
123
132
|
handleModalClick(event);
|
|
133
|
+
handleWifiGrantRedirect();
|
|
124
134
|
onClick?.(event);
|
|
125
135
|
}
|
|
126
136
|
|
|
@@ -116,7 +116,7 @@ export const FormFieldElement: React.FC<FormFieldElementProps> = props => {
|
|
|
116
116
|
} else if (fieldType === "Imageupload") {
|
|
117
117
|
const nextFiles = Array.isArray(event) ? event : [];
|
|
118
118
|
context?.setFiles(nextFiles);
|
|
119
|
-
updateFormData(
|
|
119
|
+
updateFormData("files", nextFiles);
|
|
120
120
|
return;
|
|
121
121
|
} else {
|
|
122
122
|
const { name, value } = event.target;
|
package/src/utils/index.ts
CHANGED
|
@@ -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
|
+
}
|