{"version":3,"file":"js/49354-3e02e279df5fc995baac.js","mappings":"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","sources":["webpack://StravaModern/../src/observe.ts","webpack://StravaModern/../src/InView.tsx","webpack://StravaModern/./node_modules/@strava/smart-banner-ui/node_modules/@strava/ui/Button/styles.module.scss?db9d","webpack://StravaModern/./node_modules/@strava/smart-banner-ui/node_modules/@strava/ui/Button/Button.tsx","webpack://StravaModern/./node_modules/@strava/smart-banner-ui/node_modules/@strava/ui/Button/LinkButton.tsx","webpack://StravaModern/./node_modules/@strava/smart-banner-ui/node_modules/@strava/ui/clsx/clsx.ts","webpack://StravaModern/./node_modules/@strava/smart-banner-ui/node_modules/@strava/icons/dist/ActionsCancelNormalXsmall.js","webpack://StravaModern/./node_modules/@strava/smart-banner-ui/node_modules/@strava/constants/src/appConstants.js","webpack://StravaModern/./node_modules/@strava/smart-banner-ui/node_modules/@strava/utils/src/utils.ts","webpack://StravaModern/./node_modules/@strava/smart-banner-ui/SmartBanner.module.scss?b4e9","webpack://StravaModern/./node_modules/@strava/smart-banner-ui/SmartBanner.js","webpack://StravaModern/../src/useInView.tsx"],"sourcesContent":["import type { ObserverInstanceCallback } from './index';\n\nconst observerMap = new Map<\n string,\n {\n id: string;\n observer: IntersectionObserver;\n elements: Map>;\n }\n>();\n\nconst RootIds: WeakMap = new WeakMap();\nlet rootId = 0;\n\nlet unsupportedValue: boolean | undefined = undefined;\n\n/**\n * What should be the default behavior if the IntersectionObserver is unsupported?\n * Ideally the polyfill has been loaded, you can have the following happen:\n * - `undefined`: Throw an error\n * - `true` or `false`: Set the `inView` value to this regardless of intersection state\n * **/\nexport function defaultFallbackInView(inView: boolean | undefined) {\n unsupportedValue = inView;\n}\n\n/**\n * Generate a unique ID for the root element\n * @param root\n */\nfunction getRootId(root: IntersectionObserverInit['root']) {\n if (!root) return '0';\n if (RootIds.has(root)) return RootIds.get(root);\n rootId += 1;\n RootIds.set(root, rootId.toString());\n return RootIds.get(root);\n}\n\n/**\n * Convert the options to a string Id, based on the values.\n * Ensures we can reuse the same observer when observing elements with the same options.\n * @param options\n */\nexport function optionsToId(options: IntersectionObserverInit) {\n return Object.keys(options)\n .sort()\n .filter(\n (key) => options[key as keyof IntersectionObserverInit] !== undefined,\n )\n .map((key) => {\n return `${key}_${\n key === 'root'\n ? getRootId(options.root)\n : options[key as keyof IntersectionObserverInit]\n }`;\n })\n .toString();\n}\n\nfunction createObserver(options: IntersectionObserverInit) {\n // Create a unique ID for this observer instance, based on the root, root margin and threshold.\n let id = optionsToId(options);\n let instance = observerMap.get(id);\n\n if (!instance) {\n // Create a map of elements this observer is going to observe. Each element has a list of callbacks that should be triggered, once it comes into view.\n const elements = new Map>();\n let thresholds: number[] | readonly number[];\n\n const observer = new IntersectionObserver((entries) => {\n entries.forEach((entry) => {\n // While it would be nice if you could just look at isIntersecting to determine if the component is inside the viewport, browsers can't agree on how to use it.\n // -Firefox ignores `threshold` when considering `isIntersecting`, so it will never be false again if `threshold` is > 0\n const inView =\n entry.isIntersecting &&\n thresholds.some((threshold) => entry.intersectionRatio >= threshold);\n\n // @ts-ignore support IntersectionObserver v2\n if (options.trackVisibility && typeof entry.isVisible === 'undefined') {\n // The browser doesn't support Intersection Observer v2, falling back to v1 behavior.\n // @ts-ignore\n entry.isVisible = inView;\n }\n\n elements.get(entry.target)?.forEach((callback) => {\n callback(inView, entry);\n });\n });\n }, options);\n\n // Ensure we have a valid thresholds array. If not, use the threshold from the options\n thresholds =\n observer.thresholds ||\n (Array.isArray(options.threshold)\n ? options.threshold\n : [options.threshold || 0]);\n\n instance = {\n id,\n observer,\n elements,\n };\n\n observerMap.set(id, instance);\n }\n\n return instance;\n}\n\n/**\n * @param element - DOM Element to observe\n * @param callback - Callback function to trigger when intersection status changes\n * @param options - Intersection Observer options\n * @param fallbackInView - Fallback inView value.\n * @return Function - Cleanup function that should be triggered to unregister the observer\n */\nexport function observe(\n element: Element,\n callback: ObserverInstanceCallback,\n options: IntersectionObserverInit = {},\n fallbackInView = unsupportedValue,\n) {\n if (\n typeof window.IntersectionObserver === 'undefined' &&\n fallbackInView !== undefined\n ) {\n const bounds = element.getBoundingClientRect();\n callback(fallbackInView, {\n isIntersecting: fallbackInView,\n target: element,\n intersectionRatio:\n typeof options.threshold === 'number' ? options.threshold : 0,\n time: 0,\n boundingClientRect: bounds,\n intersectionRect: bounds,\n rootBounds: bounds,\n });\n return () => {\n // Nothing to cleanup\n };\n }\n // An observer with the same options can be reused, so lets use this fact\n const { id, observer, elements } = createObserver(options);\n\n // Register the callback listener for this element\n let callbacks = elements.get(element) || [];\n if (!elements.has(element)) {\n elements.set(element, callbacks);\n }\n\n callbacks.push(callback);\n observer.observe(element);\n\n return function unobserve() {\n // Remove the callback from the callback list\n callbacks.splice(callbacks.indexOf(callback), 1);\n\n if (callbacks.length === 0) {\n // No more callback exists for element, so destroy it\n elements.delete(element);\n observer.unobserve(element);\n }\n\n if (elements.size === 0) {\n // No more elements are being observer by this instance, so destroy it\n observer.disconnect();\n observerMap.delete(id);\n }\n };\n}\n","import * as React from 'react';\nimport type { IntersectionObserverProps, PlainChildrenProps } from './index';\nimport { observe } from './observe';\n\ntype State = {\n inView: boolean;\n entry?: IntersectionObserverEntry;\n};\n\nfunction isPlainChildren(\n props: IntersectionObserverProps | PlainChildrenProps,\n): props is PlainChildrenProps {\n return typeof props.children !== 'function';\n}\n\n/**\n ## Render props\n\n To use the `` component, you pass it a function. It will be called\n whenever the state changes, with the new value of `inView`. In addition to the\n `inView` prop, children also receive a `ref` that should be set on the\n containing DOM element. This is the element that the IntersectionObserver will\n monitor.\n\n If you need it, you can also access the\n [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry)\n on `entry`, giving you access to all the details about the current intersection\n state.\n\n ```jsx\n import { InView } from 'react-intersection-observer';\n\n const Component = () => (\n \n {({ inView, ref, entry }) => (\n
\n

{`Header inside viewport ${inView}.`}

\n
\n )}\n
\n );\n\n export default Component;\n ```\n\n ## Plain children\n\n You can pass any element to the ``, and it will handle creating the\n wrapping DOM element. Add a handler to the `onChange` method, and control the\n state in your own component. Any extra props you add to `` will be\n passed to the HTML element, allowing you set the `className`, `style`, etc.\n\n ```jsx\n import { InView } from 'react-intersection-observer';\n\n const Component = () => (\n console.log('Inview:', inView)}>\n

Plain children are always rendered. Use onChange to monitor state.

\n
\n );\n\n export default Component;\n ```\n */\nexport class InView extends React.Component<\n IntersectionObserverProps | PlainChildrenProps,\n State\n> {\n constructor(props: IntersectionObserverProps | PlainChildrenProps) {\n super(props);\n this.state = {\n inView: !!props.initialInView,\n entry: undefined,\n };\n }\n\n componentDidUpdate(prevProps: IntersectionObserverProps) {\n // If a IntersectionObserver option changed, reinit the observer\n if (\n prevProps.rootMargin !== this.props.rootMargin ||\n prevProps.root !== this.props.root ||\n prevProps.threshold !== this.props.threshold ||\n prevProps.skip !== this.props.skip ||\n prevProps.trackVisibility !== this.props.trackVisibility ||\n prevProps.delay !== this.props.delay\n ) {\n this.unobserve();\n this.observeNode();\n }\n }\n\n componentWillUnmount() {\n this.unobserve();\n this.node = null;\n }\n\n node: Element | null = null;\n _unobserveCb: (() => void) | null = null;\n\n observeNode() {\n if (!this.node || this.props.skip) return;\n const {\n threshold,\n root,\n rootMargin,\n trackVisibility,\n delay,\n fallbackInView,\n } = this.props;\n\n this._unobserveCb = observe(\n this.node,\n this.handleChange,\n {\n threshold,\n root,\n rootMargin,\n // @ts-ignore\n trackVisibility,\n // @ts-ignore\n delay,\n },\n fallbackInView,\n );\n }\n\n unobserve() {\n if (this._unobserveCb) {\n this._unobserveCb();\n this._unobserveCb = null;\n }\n }\n\n handleNode = (node?: Element | null) => {\n if (this.node) {\n // Clear the old observer, before we start observing a new element\n this.unobserve();\n\n if (!node && !this.props.triggerOnce && !this.props.skip) {\n // Reset the state if we get a new node, and we aren't ignoring updates\n this.setState({ inView: !!this.props.initialInView, entry: undefined });\n }\n }\n\n this.node = node ? node : null;\n this.observeNode();\n };\n\n handleChange = (inView: boolean, entry: IntersectionObserverEntry) => {\n if (inView && this.props.triggerOnce) {\n // If `triggerOnce` is true, we should stop observing the element.\n this.unobserve();\n }\n if (!isPlainChildren(this.props)) {\n // Store the current State, so we can pass it to the children in the next render update\n // There's no reason to update the state for plain children, since it's not used in the rendering.\n this.setState({ inView, entry });\n }\n if (this.props.onChange) {\n // If the user is actively listening for onChange, always trigger it\n this.props.onChange(inView, entry);\n }\n };\n\n render() {\n const { children } = this.props;\n if (typeof children === 'function') {\n const { inView, entry } = this.state;\n return children({ inView, entry, ref: this.handleNode });\n }\n\n const {\n as,\n triggerOnce,\n threshold,\n root,\n rootMargin,\n onChange,\n skip,\n trackVisibility,\n delay,\n initialInView,\n fallbackInView,\n ...props\n } = this.props as PlainChildrenProps;\n\n return React.createElement(\n as || 'div',\n { ref: this.handleNode, ...props },\n children,\n );\n }\n}\n","// extracted by mini-css-extract-plugin\nexport default {\"btn\":\"Button--btn--ioOdD\",\"default\":\"Button--default--BRVHD\",\"primary\":\"Button--primary--i3WKp\",\"primaryOutline\":\"Button--primaryOutline--0VEUP\",\"secondary\":\"Button--secondary--Hqzix\",\"icon\":\"Button--icon--M+ZI8\",\"text\":\"Button--text--dGDlJ\"};","import React from 'react';\nimport clsx from 'clsx';\n\nimport styles from './styles.module.scss';\n\nexport interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> {\n children: React.ReactNode;\n variant?:\n | 'default'\n | 'primary'\n | 'primaryOutline'\n | 'secondary'\n | 'icon'\n | 'text';\n className?: string;\n}\n\nconst Button = React.forwardRef(\n ({ children, className, variant = 'default', ...props }, ref) => (\n \n {children}\n \n )\n);\n\nexport default Button;\n","import clsx from 'clsx';\nimport React, { ComponentPropsWithoutRef, forwardRef } from 'react';\n\nimport styles from './styles.module.scss';\nimport { ButtonProps } from './Button';\n\nexport type LinkButtonProps = ButtonProps & ComponentPropsWithoutRef<'a'>;\n\nconst LinkButton = forwardRef(\n ({ href, children, className, variant = 'default', ...otherProps }, ref) => {\n return (\n \n {children}\n \n );\n }\n);\n\nexport default LinkButton;\n","type Value = string | number | boolean | undefined | null;\ntype MixObject = {\n [key: string]: boolean | Mix | undefined | null;\n};\ntype MixArray = Array;\ntype Mix = Value | MixObject | MixArray;\n\nfunction toVal(mix: Mix): string {\n let k,\n y,\n str = '';\n\n if (typeof mix === 'string' || typeof mix === 'number') {\n str += mix;\n } else if (typeof mix === 'object') {\n if (Array.isArray(mix)) {\n for (k = 0; k < mix.length; k++) {\n if (mix[k]) {\n if ((y = toVal(mix[k]))) {\n str && (str += ' ');\n str += y;\n }\n }\n }\n } else {\n for (k in mix) {\n if (!mix || mix[k]) {\n str && (str += ' ');\n str += k;\n }\n }\n }\n }\n\n return str;\n}\n\nexport function clsx(..._args: Mix[]): string {\n let i = 0,\n tmp,\n x,\n str = '';\n\n while (i < arguments.length) {\n if ((tmp = arguments[i++])) {\n if ((x = toVal(tmp))) {\n str && (str += ' ');\n str += x;\n }\n }\n }\n return str;\n}\n\nexport default clsx;\n","import * as React from \"react\";\nimport PropTypes from \"prop-types\";\nconst SvgActionsCancelNormalXsmall = React.forwardRef(\n ({ color, size, title, titleId, ...props }, ref) => (\n \n {title ? {title} : null}\n \n \n )\n);\nSvgActionsCancelNormalXsmall.defaultProps = {\n color: \"currentColor\",\n size: 16,\n title: undefined,\n titleId: undefined,\n};\nSvgActionsCancelNormalXsmall.propTypes = {\n color: PropTypes.string,\n size: PropTypes.number,\n title: PropTypes.string,\n titleId: PropTypes.string,\n};\nexport default SvgActionsCancelNormalXsmall;\n","export const YOUNGEST_AGE_ON_PLATFORM = 13;\n\nexport const YOUNGEST_AGE_FOR_HEALTH_AND_PROMO = 16;\n\nexport const GENDER = Object.freeze({\n man: 'man',\n woman: 'woman',\n preferNotSay: 'prefer_not_say',\n nonBinary: 'nonbinary'\n});\n\nexport const LOCALE = Object.freeze({\n deDE: 'de-DE',\n enUS: 'en-US',\n enGB: 'en-GB',\n esES: 'es-ES',\n es419: 'es-419',\n frFR: 'fr-FR',\n itIT: 'it-IT',\n jaJP: 'ja-JP',\n koKR: 'ko-KR',\n nlNL: 'nl-NL',\n ptBR: 'pt-BR',\n ptPT: 'pt-PT',\n ruRU: 'ru-RU',\n zhCN: 'zh-CN',\n zhTW: 'zh-TW'\n});\n\n// If a language has more than one locale, the first in the list is the default\nexport const LANGUAGE_LOCALES = Object.freeze({\n de: [LOCALE.deDE],\n // en is intentionally omitted here since English is the global default\n es: [LOCALE.esES, LOCALE.es419],\n fr: [LOCALE.frFR],\n it: [LOCALE.itIT],\n ja: [LOCALE.jaJP],\n ko: [LOCALE.koKR],\n nl: [LOCALE.nlNL],\n pt: [LOCALE.ptPT, LOCALE.ptBR],\n ru: [LOCALE.ruRU],\n zh: [LOCALE.zhCN, LOCALE.zhTW]\n});\n\nexport const EXPERIMENT_COHORTS = Object.freeze({\n control: 'control',\n variantA: 'variant-a',\n variantB: 'variant-b'\n});\n\nexport const MOBILE_OS = Object.freeze({\n android: 'android',\n iPhone: 'iphone',\n iPad: 'ipad'\n});\n\nexport const SUPPORTED_BROWSERS = Object.freeze({\n chrome: 'chrome',\n firefox: 'firefox',\n safari: 'safari'\n});\n\n// universal link paths from 'apple-app-site-association' file in active\nexport const APPLE_APP_LINK_PATHS = [\n '/dashboard',\n '/activities/*',\n '/athletes/*',\n '/segments/*',\n '/challenges/*',\n '/videos/*',\n '/routes/*',\n '/premium/*',\n '/settings/*',\n '/shop/*',\n '/athlete/*',\n '/clubs/*',\n '/summit/join',\n '/summit/perks',\n '/oauth/mobile/authorize'\n];\n\nexport const ATHLETES_VISIBILITY = Object.freeze({\n optedOut: 'opted_out' // Strava::Athletes::Visibility::OPTED_OUT\n});\n\nexport default {\n YOUNGEST_AGE_ON_PLATFORM,\n YOUNGEST_AGE_FOR_HEALTH_AND_PROMO,\n GENDER,\n LOCALE,\n EXPERIMENT_COHORTS,\n MOBILE_OS,\n SUPPORTED_BROWSERS,\n APPLE_APP_LINK_PATHS,\n ATHLETES_VISIBILITY\n};\n","import {\n MOBILE_OS,\n SUPPORTED_BROWSERS,\n APPLE_APP_LINK_PATHS\n} from '@strava/constants/src/appConstants';\nimport { isString } from 'lodash-es';\nimport camelCase from 'lodash-es/camelCase';\nimport snakeCase from 'lodash-es/snakeCase';\nimport isObject from 'lodash-es/isObject';\nimport { CamelCasedPropertiesDeep, SnakeCasedPropertiesDeep } from 'type-fest';\n\nexport const generateRandomId = () => Math.random().toString(36).substring(2);\n\n// Capitalizes all the words and replaces some characters in the string to create a nicer looking title.\n// eg. 'man from the boondocks' => 'Man From The Boondocks'\nexport const titleize = (sentence: string) => {\n return sentence\n .split(' ')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n};\n\nexport const displayName = (\n { firstName = '', lastName = '' },\n options = { forceAnonymize: false, maxLength: 100 }\n) => {\n const { maxLength, forceAnonymize } = options;\n const fullName = `${firstName} ${lastName}`;\n\n if (forceAnonymize || fullName.length > maxLength) {\n if (firstName.length > maxLength - 3) {\n return `${titleize(firstName.substring(0, maxLength - 4))}... ${lastName\n .charAt(0)\n .toUpperCase()}.`;\n }\n return `${titleize(firstName)} ${lastName.charAt(0).toUpperCase()}.`;\n }\n return fullName.trim();\n};\n\n/**\n * Checks to see if device is mobile\n * @param {string} userAgent - user agent string for the current browser. For next-js apps (SSR),\n * this value can be retrieved from request.headers['user-agent']\n * @returns {boolean} - true if the device is mobile\n */\nexport const isMobile = (userAgent = window?.navigator?.userAgent) => {\n if (isString(userAgent)) {\n // ref - https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#mobile_tablet_or_desktop\n return userAgent.toLowerCase().includes('mobi');\n }\n return false;\n};\n\n/**\n * Determines the OS for mobile devices\n * @param {string} userAgent - user agent string for the current browser. For next-js apps (SSR),\n * this value can be retrieved from request.headers['user-agent']\n * @returns {(string|null)} - one of ['android', 'ipad', 'iphone'] or null for unknown\n */\nexport const getMobileOS = (userAgent = window?.navigator?.userAgent) => {\n if (isString(userAgent)) {\n if (userAgent.includes('Android')) {\n return MOBILE_OS.android;\n }\n if (userAgent.includes('iPad')) {\n return MOBILE_OS.iPad;\n }\n if (userAgent.includes('iPhone')) {\n return MOBILE_OS.iPhone;\n }\n }\n return null;\n};\n\n/**\n * Determines, in a limited fashion, the browser according to the user agent.\n * NOTE: UAs are notoriously difficult to parse. This is meant as a bare-bones, noncomprehensive parser,\n * based on https://www.whatismybrowser.com/guides/the-latest-user-agent/.\n *\n * The order of conditionals is important, bc some Chrome & Firefox UAs include 'Safari', too.\n * Some Edge UAs may be recognized as Chrome for now, since they can include 'Chrome'. We don't\n * officially support Edge (yet?).\n *\n * @param {string} userAgent - user agent string for the current browser. For next-js apps (SSR),\n * this value can be retrieved from request.headers['user-agent']\n * @returns {(string|null)} - one of ['chrome', 'firefox', 'safari'] or null for unknown/unsupported browser\n */\nexport const getBrowser = (userAgent = window?.navigator?.userAgent) => {\n if (isString(userAgent)) {\n if (userAgent.includes('Chrome') || userAgent.includes('CriOS')) {\n return SUPPORTED_BROWSERS.chrome;\n }\n if (userAgent.includes('Firefox') || userAgent.includes('FxiOS'))\n return SUPPORTED_BROWSERS.firefox;\n if (userAgent.includes('Safari')) {\n return SUPPORTED_BROWSERS.safari;\n }\n }\n\n return null;\n};\n\n/**\n * Universal links are determined by the paths described in `apple-app-site-association` in active.\n * @param {string} pathname - window.location.pathname or nextjs equivalent\n * @returns {boolean} - whether the pathname string matches\n */\nexport const isUniversalLinkPage = (pathname: string) => {\n if (isString(pathname)) {\n return APPLE_APP_LINK_PATHS.some((path: string) => {\n const pathWithoutAsterisk = path.replace('/*', '');\n return pathname.startsWith(pathWithoutAsterisk);\n });\n }\n return false;\n};\n\n// eg. given,\n// \"urls\": {\n// \"100\": \"https://photo_url_1\",\n// \"1800\": \"https://photo_url_2\"\n// }\n// returns https://photo_url_2\nexport const getPhotoWithMaxDimension = (\n photoUrlHash: Record\n) => {\n if (!isObject(photoUrlHash)) {\n return '';\n }\n const key = Object.keys(photoUrlHash).reduce((a, b) => (a > b ? a : b));\n return photoUrlHash[key];\n};\n\n/**\n * Immutably merges a payload into an item in an array of objects\n * @param {array} array - An array of objects\n * @param {string|number} id - The unique id of the item in the array to update\n * @param {object} payload - the value to merge into the matched item\n * @param {object} options\n * @param {string} [options.idName=id] - the key of the unique identifier\n * @param {bool} [options.upsert=false] - if true, will insert the payload into the array as a new item if no matching item is found\n * @return {array} - A copy of the original array with the matching item updated\n */\nexport function arrayUpdateItemById(\n array: T[],\n id: string | number,\n payload: Partial,\n { idName, upsert }: { idName?: keyof T; upsert?: boolean } = {}\n): T[] {\n let itemFound = false;\n const property = idName || ('id' as keyof T);\n const mappedArray = array.map((item) => {\n itemFound = itemFound || item[property] === id;\n return item[property] === id ? { ...item, ...payload } : item;\n });\n\n return itemFound || !upsert ? mappedArray : ([...array, payload] as T[]);\n}\n\n/**\n * Immutably removes an item from an array by index\n * @param {array} array\n * @param {number} index - the index of the item to remove\n * @return {array} - A copy of the original array with the matching item removed\n */\nexport function arrayRemoveByIndex(array: T[], index: number): T[] {\n return index === -1\n ? array\n : [...array.slice(0, index), ...array.slice(index + 1)];\n}\n\n/**\n * Immutably removes an item from an array of objects\n * @param {array} array - An array of objects\n * @param {string|number} id - The unique id of the item in the array to remove\n * @param {string} [idName=id] - the key of the unique identifier\n * @return {array} - A copy of the original array with the matching item removed\n */\n\nexport function arrayRemoveItemById(\n array: T[],\n propertyValue: string | number,\n idName = 'id'\n): T[] {\n const index = array.findIndex(\n (item) => item[idName as keyof T] === propertyValue\n );\n return arrayRemoveByIndex(array, index);\n}\n\nexport const capitalizeFirstLetter = (string: string) =>\n `${string.charAt(0).toUpperCase()}${string.slice(1)}`;\n\n/**\n * @description Converts Object keys from string of any case to camelCase.\n * Handles nested objects and arrays.\n * */\nexport function convertKeysToCamel(object: T): CamelCasedPropertiesDeep {\n if (Array.isArray(object)) {\n return (object as unknown[]).map((item) =>\n convertKeysToCamel(item)\n ) as CamelCasedPropertiesDeep;\n }\n\n if (isObject(object)) {\n return Object.keys(object).reduce((o, k) => {\n const key = camelCase(k);\n const value = object[\n k as keyof typeof object\n ] as CamelCasedPropertiesDeep;\n\n if (isObject(value)) {\n return {\n ...o,\n [key]: convertKeysToCamel(value)\n };\n }\n\n if (Array.isArray(value)) {\n return {\n ...o,\n [key]: (value as unknown[]).map((item) => convertKeysToCamel(item))\n };\n }\n\n return {\n ...o,\n [key]: value\n };\n }, {} as CamelCasedPropertiesDeep);\n }\n\n return object as CamelCasedPropertiesDeep;\n}\n\n/**\n * @description Converts Object keys from string of any case to snakeCase.\n * Handles nested objects and arrays.\n * */\nexport function convertKeysToSnake(object: T): SnakeCasedPropertiesDeep {\n if (Array.isArray(object)) {\n return (object as unknown[]).map((item) =>\n convertKeysToSnake(item)\n ) as SnakeCasedPropertiesDeep;\n }\n\n if (isObject(object)) {\n return Object.keys(object).reduce((o, k) => {\n const key = snakeCase(k);\n const value = object[\n k as keyof typeof object\n ] as SnakeCasedPropertiesDeep;\n\n if (isObject(value)) {\n return {\n ...o,\n [key]: convertKeysToSnake(value)\n };\n }\n\n if (Array.isArray(value)) {\n return {\n ...o,\n [key]: (value as unknown[]).map((item) => convertKeysToSnake(item))\n };\n }\n\n return {\n ...o,\n [key]: value\n };\n }, {} as SnakeCasedPropertiesDeep);\n }\n\n return object as SnakeCasedPropertiesDeep;\n}\n\n/**\n * Checks to see if localStorage is available\n *\n * @return {boolean} - true if localStorage can be used\n */\nexport const isLocalStorageAvailable = () => {\n const test = 'test';\n try {\n localStorage.setItem(test, test);\n localStorage.removeItem(test);\n return true;\n } catch (e) {\n return false;\n }\n};\n\n/**\n * Appends querystring params to a URL\n *\n * @param {string} originalUrl - The original URL to append params to\n * @param {object} params - An object of key value parameters to append to URL\n * @return {string} - URL with params appended\n */\nexport const addParamsToURL = (originalUrl: string, params: object) => {\n const url = new URL(originalUrl);\n\n Object.entries(params).forEach(([key, value]) => {\n url.searchParams.append(key, value);\n });\n\n return url.toString();\n};\n\n/**\n * Some of our logged-out pages have a full-screen image background.\n * This util helps with setting the background image on the `` element, and\n * just requires passing in the image to be used.\n * @param {string} background\n */\nexport const setFullScreenBackgroundImage = (background: string) => {\n // apply background image directly to ``\n const body = document.querySelector('body');\n if (body) {\n body.style.setProperty('background-image', `url(${background})`);\n body.style.setProperty('background-size', 'cover');\n body.style.setProperty('background-position', 'center');\n }\n};\n\n/**\n * Uses the host to determine if a page is being loaded in staging or localhost.\n *\n * This util function is helpful for apps that run on nextJS which has node process\n * defined as production in both staging and production.\n *\n * @param host - host name\n * @returns {boolean} - true if app is running in staging or local\n */\nexport const isStagingOrLocal = (host: string) =>\n ['staging', 'localhost'].some((_host) => host.includes(_host));\n\n/**\n * Adds an id property to each object in an array. Uses the object's index as the value for the id\n * @param {array} objects - An array of objects\n * @return {array} - A copy of the original array with id property added to each object\n */\nexport const arrayAddIndexAsId = (objects: Array) =>\n objects.map((object, index) => ({ ...object, id: index }));\n\n/**\n * Uses the host to determine if the protocol should be http or https\n *\n * @param host - host name\n * @returns {string} - Protocol (http / https)\n */\nexport const getProtocolForHost = (host: string) => {\n return host.includes('localhost') ? 'http' : 'https';\n};\n\n/**\n * Constructs the site root URL\n *\n * @param host - host name\n * @returns {string} - Root URL with protocol\n */\nexport const getRootUrl = (host: string) => {\n return `${getProtocolForHost(host)}://${host}`;\n};\n\n/**\n * Constructs the athlete URL for pro and non-pro athletes, i.e.\n * https://www.strava.com/athletes/[athlete_id] or\n * https://www.strava.com/pros/[athlete_id]\n *\n * @param isPro - boolean indicating if the athlete is a pro or not\n * @param athleteId - athlete unique identifier\n * @param host - host name\n * @returns {string} - Root URL with protocol\n */\nexport const getAthleteUrl = (\n isPro: boolean,\n athleteId: number,\n host: string\n) => {\n return `${getProtocolForHost(host)}://${host}/${\n isPro ? 'pros' : 'athletes'\n }/${athleteId.toString()}`;\n};\n","// extracted by mini-css-extract-plugin\nexport default {\"wrapper\":\"smart-banner-ui--wrapper--8LVP-\",\"smartBanner\":\"smart-banner-ui--smartBanner--a9rNI\",\"appInfo\":\"smart-banner-ui--appInfo--5Nk-F\",\"btn\":\"smart-banner-ui--btn--JwtOK\",\"closeBtn\":\"smart-banner-ui--closeBtn--kuyi3\"};","import React, {\n useEffect,\n useState,\n useCallback,\n useMemo,\n forwardRef\n} from 'react';\nimport PropTypes from 'prop-types';\nimport { useInView } from 'react-intersection-observer';\nimport Button, { LinkButton } from '@strava/ui/Button';\nimport clsx from '@strava/ui/clsx';\nimport {\n useStravaAnalytics,\n useStravaTranslations,\n useStravaBranch\n} from '@strava/container-context';\n\nimport ActionsCancelNormalXsmall from '@strava/icons/ActionsCancelNormalXsmall';\nimport { color } from '@strava/ui-tokens/js';\nimport { MOBILE_OS } from '@strava/constants/src/appConstants';\nimport { isLocalStorageAvailable } from '@strava/utils/src/utils';\nimport styles from './SmartBanner.module.scss';\n\nimport iOSIcon from './assets/icon-ios-app.svg';\nimport androidIcon from './assets/icon-android-app.svg';\n\nexport const I18N_PREFIX = 'microfrontends.mobile_app_download_banner';\nexport const HIDE_BANNER_STORAGE_KEY = 'hide-mobile-app-download-banner';\n\nconst SmartBanner = forwardRef(\n ({ page, category, mobileOS, showCloseBtn, className }, ref) => {\n const { hasApp, appLink, isProcessing } = useStravaBranch();\n const { track: _track } = useStravaAnalytics();\n const { t: _t } = useStravaTranslations();\n\n const [inViewRef, inView] = useInView({ threshold: 0 });\n\n const [hideBanner, setHideBanner] = useState(false);\n\n const track = useCallback(\n (fields = {}) => {\n _track({\n category,\n page,\n ...fields,\n properties: {\n ...fields.properties\n }\n });\n },\n [_track, category, page]\n );\n\n const t = useMemo(\n () => (key, params) => _t(`${I18N_PREFIX}.${key}`, { ...params }),\n [_t]\n );\n\n const getAppInfoCopy = () => {\n if (\n [MOBILE_OS.iPhone, MOBILE_OS.android].includes(mobileOS?.toLowerCase())\n ) {\n return t(`app.${mobileOS.toLowerCase()}`);\n }\n return t('no_app');\n };\n\n useEffect(() => {\n let hide = false;\n if (isLocalStorageAvailable()) {\n hide = localStorage?.getItem(HIDE_BANNER_STORAGE_KEY) === 'true';\n }\n\n if (!hide && inView) {\n track({ element: 'smartbanner', action: 'screen_enter' });\n }\n setHideBanner(hide);\n }, [track, inView]);\n\n const handleCloseBanner = () => {\n track({\n element: 'dismiss_smartbanner',\n action: 'click'\n });\n setHideBanner(true);\n if (isLocalStorageAvailable()) {\n localStorage?.setItem(HIDE_BANNER_STORAGE_KEY, true);\n }\n\n // allows active to remove placeholder spacing when smartbanner is hidden\n document.dispatchEvent(new Event('removeSmartbanner'));\n };\n\n if (hideBanner) {\n return null;\n }\n\n return (\n \n
\n {showCloseBtn && (\n \n \n \n )}\n\n \n\n
\n
Strava
\n
{getAppInfoCopy()}
\n
\n \n track({\n element: hasApp ? 'open_app' : 'download_app',\n action: 'click',\n properties: {\n url: appLink\n }\n })\n }\n >\n {hasApp ? t('open') : t('download')}\n \n
\n \n );\n }\n);\n\nSmartBanner.defaultProps = {\n mobileOS: null,\n className: null,\n showCloseBtn: true\n};\n\nSmartBanner.propTypes = {\n page: PropTypes.string.isRequired,\n category: PropTypes.string.isRequired,\n mobileOS: PropTypes.string,\n className: PropTypes.string,\n showCloseBtn: PropTypes.bool\n};\n\nexport default SmartBanner;\n","import * as React from 'react';\nimport type { InViewHookResponse, IntersectionOptions } from './index';\nimport { observe } from './observe';\n\ntype State = {\n inView: boolean;\n entry?: IntersectionObserverEntry;\n};\n\n/**\n * React Hooks make it easy to monitor the `inView` state of your components. Call\n * the `useInView` hook with the (optional) [options](#options) you need. It will\n * return an array containing a `ref`, the `inView` status and the current\n * [`entry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry).\n * Assign the `ref` to the DOM element you want to monitor, and the hook will\n * report the status.\n *\n * @example\n * ```jsx\n * import React from 'react';\n * import { useInView } from 'react-intersection-observer';\n *\n * const Component = () => {\n * const { ref, inView, entry } = useInView({\n * threshold: 0,\n * });\n *\n * return (\n *
\n *

{`Header inside viewport ${inView}.`}

\n *
\n * );\n * };\n * ```\n */\nexport function useInView({\n threshold,\n delay,\n trackVisibility,\n rootMargin,\n root,\n triggerOnce,\n skip,\n initialInView,\n fallbackInView,\n onChange,\n}: IntersectionOptions = {}): InViewHookResponse {\n const [ref, setRef] = React.useState(null);\n const callback = React.useRef();\n const [state, setState] = React.useState({\n inView: !!initialInView,\n entry: undefined,\n });\n\n // Store the onChange callback in a `ref`, so we can access the latest instance\n // inside the `useEffect`, but without triggering a rerender.\n callback.current = onChange;\n\n React.useEffect(\n () => {\n // Ensure we have node ref, and that we shouldn't skip observing\n if (skip || !ref) return;\n\n let unobserve: (() => void) | undefined;\n unobserve = observe(\n ref,\n (inView, entry) => {\n setState({\n inView,\n entry,\n });\n if (callback.current) callback.current(inView, entry);\n\n if (entry.isIntersecting && triggerOnce && unobserve) {\n // If it should only trigger once, unobserve the element after it's inView\n unobserve();\n unobserve = undefined;\n }\n },\n {\n root,\n rootMargin,\n threshold,\n // @ts-ignore\n trackVisibility,\n // @ts-ignore\n delay,\n },\n fallbackInView,\n );\n\n return () => {\n if (unobserve) {\n unobserve();\n }\n };\n },\n // We break the rule here, because we aren't including the actual `threshold` variable\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [\n // If the threshold is an array, convert it to a string, so it won't change between renders.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n Array.isArray(threshold) ? threshold.toString() : threshold,\n ref,\n root,\n rootMargin,\n triggerOnce,\n skip,\n trackVisibility,\n fallbackInView,\n delay,\n ],\n );\n\n const entryTarget = state.entry?.target;\n const previousEntryTarget = React.useRef();\n if (\n !ref &&\n entryTarget &&\n !triggerOnce &&\n !skip &&\n previousEntryTarget.current !== entryTarget\n ) {\n // If we don't have a node ref, then reset the state (unless the hook is set to only `triggerOnce` or `skip`)\n // This ensures we correctly reflect the current state - If you aren't observing anything, then nothing is inView\n previousEntryTarget.current = entryTarget;\n setState({\n inView: !!initialInView,\n entry: undefined,\n });\n }\n\n const result = [setRef, state.inView, state.entry] as InViewHookResponse;\n\n // Support object destructuring, by adding the specific values.\n result.ref = result[0];\n result.inView = result[1];\n result.entry = result[2];\n\n return result;\n}\n"],"names":["observerMap","Map","RootIds","WeakMap","rootId","unsupportedValue","optionsToId","options","Object","keys","sort","filter","key","map","root","has","set","toString","get","observe","element","callback","fallbackInView","window","IntersectionObserver","bounds","getBoundingClientRect","isIntersecting","target","intersectionRatio","threshold","time","boundingClientRect","intersectionRect","rootBounds","id","observer","elements","instance","thresholds","entries","forEach","entry","inView","some","trackVisibility","isVisible","Array","isArray","createObserver","callbacks","push","splice","indexOf","length","delete","unobserve","size","disconnect","React","ref","children","className","variant","props","_ref","clsx","styles","btn","forwardRef","href","otherProps","toVal","mix","k","y","str","_args","_key","arguments","tmp","x","i","SvgActionsCancelNormalXsmall","color","title","titleId","fill","xmlns","viewBox","width","height","d","defaultProps","undefined","propTypes","PropTypes","freeze","man","woman","preferNotSay","nonBinary","LOCALE","deDE","enUS","enGB","esES","es419","frFR","itIT","jaJP","koKR","nlNL","ptBR","ptPT","ruRU","zhCN","zhTW","MOBILE_OS","de","es","fr","it","ja","ko","nl","pt","ru","zh","control","variantA","variantB","android","iPhone","iPad","chrome","firefox","safari","optedOut","isLocalStorageAvailable","test","localStorage","setItem","removeItem","e","HIDE_BANNER_STORAGE_KEY","SmartBanner","page","category","mobileOS","showCloseBtn","hasApp","appLink","isProcessing","useStravaBranch","track","_track","useStravaAnalytics","t","_t","useStravaTranslations","inViewRef","_temp","delay","rootMargin","triggerOnce","skip","initialInView","onChange","setRef","React2","state","setState","current","entryTarget","_a","previousEntryTarget","result","useInView","hideBanner","setHideBanner","useState","useCallback","fields","properties","useMemo","params","useEffect","hide","getItem","action","Button","type","onClick","handleCloseBanner","document","dispatchEvent","Event","ActionsCancelNormalXsmall","src","toLowerCase","androidIcon","iOSIcon","alt","includes","LinkButton","disabled","url","isRequired"],"sourceRoot":""}