= {\n color,\n fontWeight,\n fontStyle,\n textAlign,\n fontSize,\n fontFamily\n };\n\n return (\n <>\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n >\n );\n};\n","import { Translate } from 'react-redux-i18n';\n\nimport { Tooltip } from '@mui/material';\n\nimport { useNode } from '@craftjs/core';\n\nimport { OptionalTypographyProps } from 'builder/constants/types';\n\nimport { TextDefaultProps, TextProps, TextSettings } from '../Text/Text.settings';\n\nexport const ProgressText = ({ colon, ...rest }: TextProps & OptionalTypographyProps) => {\n const {\n connectors: { connect, drag }\n } = useNode();\n\n return (\n connect(drag(ref as HTMLDivElement))} style={rest}>\n }>\n \n \n {colon ? ':' : ''}\n \n \n
\n );\n};\n\nProgressText.craft = {\n displayName: 'Progress Text',\n props: { ...TextDefaultProps, colon: false },\n related: { settings: TextSettings }\n};\n","import { Translate } from 'react-redux-i18n';\n\nimport { Tooltip } from '@mui/material';\n\nimport { useNode } from '@craftjs/core';\n\nimport { TextDefaultProps, TextProps, TextSettings } from '../Text/Text.settings';\n\nexport const RewardText = (props: TextProps) => {\n const {\n connectors: { connect, drag }\n } = useNode();\n\n return (\n connect(drag(ref as HTMLDivElement))} style={props}>\n }>\n \n \n \n \n
\n );\n};\n\nRewardText.craft = {\n displayName: 'Reward Text',\n props: TextDefaultProps,\n related: { settings: TextSettings }\n};\n","import { useEffect, useRef } from 'react';\nimport ContentEditable from 'react-contenteditable';\n\nimport { Box } from '@mui/material';\n\nimport { useNode } from '@craftjs/core';\n\nimport { useBuilderContext } from '../../../../context/useBuilderContext';\nimport { TextDefaultProps, TextProps, TextSettings } from './Text.settings';\n\nexport const Text = (props: TextProps) => {\n const {\n connectors: { connect },\n hasSelectedNode,\n actions: { setProp }\n } = useNode((state) => ({\n hasSelectedNode: Boolean(state.events.selected),\n hasDraggedNode: Boolean(state.events.dragged)\n }));\n\n const textInput = useRef(null);\n\n const { focusTextInput, setEditable, editable } = useBuilderContext();\n\n useEffect(() => {\n if (!hasSelectedNode) setEditable(false);\n }, [hasSelectedNode, setEditable]);\n\n const { text, ...CSS } = props;\n\n return (\n connect(ref as any)}\n onDoubleClick={() => {\n focusTextInput(textInput.current);\n }}>\n {\n setProp((pr: TextProps) => {\n pr.text = e.target.value.replace(/<\\/?[^>]+(>|$)/g, '');\n return pr;\n });\n }}\n tagName=\"div\"\n style={CSS}\n />\n \n );\n};\n\nText.craft = {\n displayName: 'Text',\n props: TextDefaultProps,\n related: { settings: TextSettings },\n rules: { canMoveIn: () => false }\n};\n","import uuid from 'react-uuid';\n\nimport { FreshNode } from '@craftjs/core';\n\nimport { Container } from '../components/craft/BuilderParts/Container/Container';\nimport { ContainerDefaultProps, ContainerProps } from '../components/craft/BuilderParts/Container/Container.settings';\nimport { ProgressText } from '../components/craft/BuilderParts/StampCard/ProgressText';\nimport { RewardText } from '../components/craft/BuilderParts/StampCard/RewardText';\nimport { Text } from '../components/craft/BuilderParts/Text/Text';\nimport { TextDefaultProps } from '../components/craft/BuilderParts/Text/Text.settings';\nimport { useBuilderContext } from '../context/useBuilderContext';\n\ninterface BuildNodeArgs {\n width?: string;\n height?: string;\n backgroundColor?: string;\n flexDirection?: string;\n}\n\nexport const buildNode = ({\n width = '60%',\n height = '30%',\n backgroundColor = 'white',\n flexDirection = 'column'\n}: BuildNodeArgs): Required => ({\n id: uuid(),\n data: {\n type: Container,\n props: {\n ...ContainerDefaultProps,\n backgroundColor,\n height,\n width,\n flexDirection\n },\n isCanvas: true\n }\n});\n\nexport const buildStampCardTextContainerNode = (props?: ContainerProps): Required => ({\n id: uuid(),\n data: {\n isCanvas: true,\n type: Container,\n props: {\n ...ContainerDefaultProps,\n backgroundColor: 'white',\n height: '50%',\n width: '100%',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n ...(props || {})\n }\n }\n});\n\nexport const buildTextNode = (props: { text: string }): Required => ({\n id: uuid(),\n data: {\n type: Text,\n props: { ...TextDefaultProps, ...props },\n isCanvas: false,\n name: 'Stamp card text',\n displayName: 'Stamp card text'\n }\n});\n\nexport const useBuildRewardTextNode = (): Required => {\n const { device } = useBuilderContext();\n\n return {\n id: uuid(),\n data: {\n isCanvas: true,\n type: RewardText,\n props: {\n ...TextDefaultProps,\n fontSize: device === 'phone' ? '30px' : '50px',\n fontWeight: '500',\n color: '#12890D',\n paddingLeft: device === 'phone' ? '5px' : '0px',\n paddingRight: device === 'phone' ? '5px' : '0px'\n }\n }\n };\n};\n\nexport const useBuildProgressTextNode = (): Required => {\n const { device } = useBuilderContext();\n\n return {\n id: uuid(),\n data: {\n isCanvas: true,\n type: ProgressText,\n props: {\n ...TextDefaultProps,\n fontSize: device === 'phone' ? '20px' : '33px',\n paddingLeft: device === 'phone' ? '5px' : '0px',\n paddingRight: device === 'phone' ? '5px' : '0px'\n }\n }\n };\n};\n","import { useEffect } from 'react';\n\nimport { useEditor, useNode } from '@craftjs/core';\n\nimport { buildNode } from '../../../../../utils/buildNode';\nimport { Resizer } from '../../../BuilderComponents/Resizer';\nimport { ContainerDefaultProps, ContainerProps, ContainerSettings } from '../Container.settings';\n\n/*\n┌───────────┬───────────┐\n│ │ │\n│ │ │\n│ │ │\n│ │ │\n│ │ │\n└───────────┴───────────┘\n */\n\nconst settings = [\n { backgroundColor: 'rgba(183,253,252,0.42)', height: '100%', width: '50%' }, // Left column\n { backgroundColor: 'rgba(209,183,253,0.42)', height: '100%', width: '50%' } // Right Column\n];\n\nexport const Container1x2 = (props: ContainerProps) => {\n const { query, actions } = useEditor();\n const { id } = useNode();\n\n useEffect(() => {\n if (!query.node(id).descendants().length) {\n settings.map(buildNode).forEach((col) => {\n actions.history.ignore().add(query.parseFreshNode(col).toNode(), id);\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nContainer1x2.craft = {\n displayName: 'Container 1x2',\n props: { ...ContainerDefaultProps, flexDirection: 'row' },\n rules: { canDrag: () => true },\n related: { settings: ContainerSettings }\n};\n","import { useEffect } from 'react';\n\nimport { useEditor, useNode } from '@craftjs/core';\n\nimport { buildNode } from '../../../../../utils/buildNode';\nimport { Resizer } from '../../../BuilderComponents/Resizer';\nimport { ContainerDefaultProps, ContainerProps, ContainerSettings } from '../Container.settings';\n\n/*\n┌──────┬──────┬──────┐\n│ │ │ │\n│ │ │ │\n│ │ │ │\n│ │ │ │\n│ │ │ │\n│ │ │ │\n└──────┴──────┴──────┘\n */\n\n// prettier-ignore\nconst settings = [\n { backgroundColor: 'rgba(183,253,252,0.42)', height: '100%', width: '33.33333%' }, // Left column\n { backgroundColor: 'rgba(209,183,253,0.42)', height: '100%', width: '33.33333%' }, // Middle Column\n { backgroundColor: 'rgba(188,253,183,0.42)', height: '100%', width: '33.33333%' } // Right Column\n];\n\nexport const Container1x3 = (props: ContainerProps) => {\n const { query, actions } = useEditor();\n const { id } = useNode();\n\n useEffect(() => {\n if (!query.node(id).descendants().length) {\n settings.map(buildNode).forEach((col) => {\n actions.history.ignore().add(query.parseFreshNode(col).toNode(), id);\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nContainer1x3.craft = {\n displayName: 'Container 1x3',\n props: { ...ContainerDefaultProps, flexDirection: 'row' },\n rules: { canDrag: () => true },\n related: { settings: ContainerSettings }\n};\n","import { useEffect } from 'react';\n\nimport { useEditor, useNode } from '@craftjs/core';\n\nimport { buildNode } from '../../../../../utils/buildNode';\nimport { Resizer } from '../../../BuilderComponents/Resizer';\nimport { ContainerDefaultProps, ContainerProps, ContainerSettings } from '../Container.settings';\n\n/*\n┌────────────────────┐\n│ │\n│ │\n│ │\n├────────────────────┤\n│ │\n│ │\n│ │\n└────────────────────┘\n */\n\nconst settings = [\n { backgroundColor: 'rgba(183,253,252,0.42)', height: '50%', width: '100%' }, // Top row\n { backgroundColor: 'rgba(209,183,253,0.42)', height: '50%', width: '100%' } // Bottom row\n];\n\nexport const Container2x1 = (props: ContainerProps) => {\n const { query, actions } = useEditor();\n const { id } = useNode();\n\n useEffect(() => {\n if (!query.node(id).descendants().length) {\n settings.map(buildNode).forEach((row) => {\n actions.history.ignore().add(query.parseFreshNode(row).toNode(), id);\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nContainer2x1.craft = {\n displayName: 'Container 2x1',\n props: ContainerDefaultProps,\n rules: { canDrag: () => true },\n related: { settings: ContainerSettings }\n};\n","import { useEffect } from 'react';\n\nimport { useEditor, useNode } from '@craftjs/core';\n\nimport { buildNode } from '../../../../../utils/buildNode';\nimport { Resizer } from '../../../BuilderComponents/Resizer';\nimport { ContainerDefaultProps, ContainerProps, ContainerSettings } from '../Container.settings';\n\n/*\n┌──────────┬──────────┐\n│ │ │\n│ │ │\n│ │ │\n├──────────┼──────────┤\n│ │ │\n│ │ │\n│ │ │\n└──────────┴──────────┘\n */\n\n// prettier-ignore\nconst settings = [\n { height: '50%', width: '100%', flexDirection: 'row' }, // Top row\n { height: '50%', width: '100%', flexDirection: 'row' }, // Bottom row\n { height: '100%', width: '50%', backgroundColor: 'rgba(183,253,252,0.42)' }, // Top row left square\n { height: '100%', width: '50%', backgroundColor: 'rgba(183, 253, 216, 0.76)' }, // Top row right square\n { height: '100%', width: '50%', backgroundColor: 'rgba(209, 183, 253, 0.42)' }, // Bottom row left square\n { height: '100%', width: '50%', backgroundColor: 'rgba(235, 8, 152, 0.42)' } // Bottom row right square\n];\n\nexport const Container2x2 = (props: ContainerProps) => {\n const { query, actions } = useEditor();\n const { id } = useNode();\n\n useEffect(() => {\n if (!query.node(id).descendants().length) {\n settings.map(buildNode).forEach((item, index, rows) => {\n actions.history.ignore().add(\n query.parseFreshNode(item).toNode(),\n // eslint-disable-next-line no-nested-ternary\n index < 2 ? id : index < 4 ? rows[0].id : rows[1].id\n );\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nContainer2x2.craft = {\n displayName: 'Container 2x2',\n props: ContainerDefaultProps,\n rules: { canDrag: () => true },\n related: { settings: ContainerSettings }\n};\n","import { useEffect } from 'react';\n\nimport { useEditor, useNode } from '@craftjs/core';\n\nimport { buildNode } from '../../../../../utils/buildNode';\nimport { Resizer } from '../../../BuilderComponents/Resizer';\nimport { ContainerDefaultProps, ContainerProps, ContainerSettings } from '../Container.settings';\n\n/*\n┌────────────────────┐\n│ │\n│ │\n├────────────────────┤\n│ │\n│ │\n├────────────────────┤\n│ │\n│ │\n└────────────────────┘\n */\n\n// prettier-ignore\nconst settings = [\n { backgroundColor: 'rgba(11, 187, 67, 0.42)', height: '33.3333333333333%', width: '100%' }, // Top row\n { backgroundColor: 'rgba(235, 239, 5, 0.42)', height: '33.3333333333333%', width: '100%' }, // Middle row\n { backgroundColor: 'rgba(44, 11, 187, 0.42)', height: '33.3333333333333%', width: '100%' } // Bottom row\n];\n\nexport const Container3x1 = (props: ContainerProps) => {\n const { query, actions } = useEditor();\n const { id } = useNode();\n\n useEffect(() => {\n if (!query.node(id).descendants().length) {\n settings.map(buildNode).forEach((row) => {\n actions.history.ignore().add(query.parseFreshNode(row).toNode(), id);\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nContainer3x1.craft = {\n displayName: 'Container 3x1',\n props: ContainerDefaultProps,\n rules: { canDrag: () => true },\n related: { settings: ContainerSettings }\n};\n","import { useEffect } from 'react';\n\nimport { useEditor, useNode } from '@craftjs/core';\n\nimport { buildNode } from '../../../../../utils/buildNode';\nimport { Resizer } from '../../../BuilderComponents/Resizer';\nimport { ContainerDefaultProps, ContainerProps, ContainerSettings } from '../Container.settings';\n\n/*\n┌────┬───────────┐\n│ │ │\n│ │ │\n│ ├─────┬─────┤\n│ │ │ │\n│ │ │ │\n└────┴─────┴─────┘\n */\n\nconst settings = [\n { backgroundColor: 'red', height: '100%', width: '30%' }, // Left column\n { height: '100%', width: '70%' }, // Right column\n { backgroundColor: 'orange', height: '50%', width: '100%' }, // Right column row 1\n { flexDirection: 'row', height: '50%', width: '100%' }, // Right column row 1\n { backgroundColor: 'yellow', height: '100%', width: '50%' }, // Right column row 2 left square\n { backgroundColor: 'cyan', height: '100%', width: '50%' } // Right column row 2 right square\n];\n\nexport const ContainerCustom1 = (props: ContainerProps) => {\n const { query, actions } = useEditor();\n const { id } = useNode();\n\n useEffect(() => {\n if (!query.node(id).descendants().length) {\n const [cl1, cl2, cl2rw1, cl2rw2, cl2rw2sq1, cl2rw2sq2] = settings\n .map(buildNode)\n .map((item) => query.parseFreshNode(item).toNode());\n\n actions.history.ignore().add(cl1, id);\n actions.history.ignore().add(cl2, id);\n actions.history.ignore().add(cl2rw1, cl2.id);\n actions.history.ignore().add(cl2rw2, cl2.id);\n actions.history.ignore().add(cl2rw2sq1, cl2rw2.id);\n actions.history.ignore().add(cl2rw2sq2, cl2rw2.id);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nContainerCustom1.craft = {\n displayName: 'Container Custom 1',\n props: { ...ContainerDefaultProps, flexDirection: 'row' },\n rules: { canDrag: () => true },\n related: { settings: ContainerSettings }\n};\n","import { useEffect } from 'react';\n\nimport { useEditor, useNode } from '@craftjs/core';\n\nimport { buildNode } from '../../../../../utils/buildNode';\nimport { Resizer } from '../../../BuilderComponents/Resizer';\nimport { ContainerDefaultProps, ContainerProps, ContainerSettings } from '../Container.settings';\n\n/*\n┌───────────┬────┐\n│ │ │\n│ │ │\n├─────┬─────┤ │\n│ │ │ │\n│ │ │ │\n└─────┴─────┴────┘\n */\n\nconst settings = [\n { backgroundColor: 'red', height: '100%', width: '30%' }, // Right column\n { height: '100%', width: '70%' }, // Left column\n { backgroundColor: 'orange', height: '50%', width: '100%' }, // Left column row 1\n { flexDirection: 'row', height: '50%', width: '100%' }, // Left column row 1\n { backgroundColor: 'yellow', height: '100%', width: '50%' }, // Left column row 2 left square\n { backgroundColor: 'cyan', height: '100%', width: '50%' } // Left column row 2 right square\n];\n\nexport const ContainerCustom2 = (props: ContainerProps) => {\n const { query, actions } = useEditor();\n const { id } = useNode();\n\n useEffect(() => {\n if (!query.node(id).descendants().length) {\n const [cl1, cl2, cl1rw1, cl1rw2, cl1rw2sq1, cl1rw2sq2] = settings\n .map(buildNode)\n .map((item) => query.parseFreshNode(item).toNode());\n\n actions.history.ignore().add(cl1, id);\n actions.history.ignore().add(cl2, id);\n actions.history.ignore().add(cl1rw1, cl2.id);\n actions.history.ignore().add(cl1rw2, cl2.id);\n actions.history.ignore().add(cl1rw2sq1, cl1rw2.id);\n actions.history.ignore().add(cl1rw2sq2, cl1rw2.id);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nContainerCustom2.craft = {\n displayName: 'Container Custom 2',\n props: { ...ContainerDefaultProps, flexDirection: 'row' },\n rules: { canDrag: () => true },\n related: { settings: ContainerSettings }\n};\n","import React from 'react';\n\nimport { Box } from '@mui/material';\n\nimport { VerticalFlexBox } from 'generalComponents/BoxModifications';\n\nimport { BuilderTextField } from './BuilderMuiComponents';\n\ninterface TextInputWithDescriptionProps {\n label: string;\n description: JSX.Element;\n onBlur: (e: React.FocusEvent) => void;\n onKeyDown: (\n e: React.KeyboardEvent & {\n target: {\n value: string;\n };\n }\n ) => void;\n defaultValue?: string;\n}\n\nexport const TextInputWithDescription: React.FC = ({\n label,\n description,\n onBlur,\n onKeyDown,\n defaultValue\n}) => {\n return (\n \n {description} \n \n \n );\n};\n","import { ReactNode, SyntheticEvent, useState } from 'react';\n\nimport { Box, Typography } from '@mui/material';\n\nimport { useNode } from '@craftjs/core';\n\nimport { DecorationProps, MarginProps, PaddingProps, SizeProps } from 'builder/constants/types';\n\nimport { SettingsAccordionItem } from '../../BuilderComponents/AccordionItem';\nimport { ImageUpload } from '../../BuilderComponents/ImageUpload';\nimport { TextInputWithDescription } from '../../BuilderComponents/TextInputWithDescription';\nimport { DecorationSettings } from '../../Settings/Decoration.settings';\nimport { MarginSettings } from '../../Settings/Margin.settings';\nimport { PaddingSettings } from '../../Settings/Padding.settings';\nimport { SizeSettings } from '../../Settings/Size.settings';\n\nexport interface ImageAndVideoProps extends MarginProps, PaddingProps, DecorationProps {\n children?: ReactNode;\n backgroundColor?: string;\n display?: 'flex' | 'block' | 'inline';\n flexGrow?: number;\n width?: string;\n height?: string;\n src?: string;\n youtubeId?: string;\n}\n\nexport const BuilderImageDefaultProps: ImageAndVideoProps = {\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n\n paddingBottom: '0px',\n paddingTop: '0px',\n paddingLeft: '0px',\n paddingRight: '0px',\n\n backgroundColor: 'rgba(255, 255, 255, 0)',\n borderRadius: '0px',\n\n width: '50%',\n height: '20%',\n\n boxShadow: '#000000 0px 0px 0px 0px',\n\n src: 'https://wallpaperaccess.com/full/142778.jpg'\n};\n\nexport const BuilderImageSettings = () => {\n const {\n src,\n youtubeId,\n\n width,\n height,\n\n paddingBottom,\n paddingTop,\n paddingLeft,\n paddingRight,\n\n marginBottom,\n marginTop,\n marginLeft,\n marginRight,\n\n borderRadius,\n boxShadow,\n\n actions: { setProp }\n } = useNode((node) => ({\n src: node.data.props.src,\n youtubeId: node.data.props.youtubeId,\n\n width: node.data.props.width,\n height: node.data.props.height,\n\n paddingBottom: node.data.props.paddingBottom,\n paddingTop: node.data.props.paddingTop,\n paddingLeft: node.data.props.paddingLeft,\n paddingRight: node.data.props.paddingRight,\n\n marginBottom: node.data.props.marginBottom,\n marginTop: node.data.props.marginTop,\n marginLeft: node.data.props.marginLeft,\n marginRight: node.data.props.marginRight,\n\n backgroundColor: node.data.props.backgroundColor,\n borderRadius: node.data.props.borderRadius,\n boxShadow: node.data.props.boxShadow\n }));\n\n const updateProp = (prop: keyof ImageAndVideoProps) => (value: any) => {\n setProp((props: any) => {\n props[prop] = value;\n return props;\n });\n };\n\n const [expanded, setExpanded] = useState(false);\n\n const handleChange = (panel: string) => (_: SyntheticEvent, isExpanded: boolean) => {\n setExpanded(isExpanded ? panel : false);\n };\n\n const marginProps = {\n marginBottom,\n marginTop,\n marginLeft,\n marginRight\n } as Required;\n\n const paddingProps = {\n paddingBottom,\n paddingTop,\n paddingLeft,\n paddingRight\n } as Required;\n\n const decorationProps = {\n borderRadius,\n boxShadow\n } as Required;\n\n const sizeProps = {\n width,\n height\n } as Required;\n\n const videoSettings = typeof youtubeId === 'string';\n\n const onBlur = (e: React.FocusEvent) => updateProp('youtubeId')(e.target.value);\n\n const onEnterPress = (\n e: React.KeyboardEvent & {\n target: { value: string };\n }\n ) => {\n if (e.key === 'Enter') updateProp('youtubeId')(e.target.value);\n };\n\n // TODO: Add more image settings\n return (\n <>\n \n {videoSettings ? (\n \n \n Provide a YouTube video ID, of the video that you wish to show. This is the value\n after the 'v=' in the URL.\n \n \n For example, the video ID used as a default in the field below comes from the URL:\n https://www.youtube.com/watch?v=oh9SD7PItf4&ab_channel=PayAtt\n \n \n }\n defaultValue={youtubeId}\n onBlur={onBlur}\n onKeyDown={onEnterPress}\n label=\"YouTube Video ID\"\n />\n ) : (\n \n )}\n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n >\n );\n};\n","import { Resizer } from '../../BuilderComponents/Resizer';\nimport { BuilderImageDefaultProps, BuilderImageSettings, ImageAndVideoProps } from './ImageAndVideo.settings';\n\nexport const Logo = (props: ImageAndVideoProps) => {\n const { src, ...rest } = props;\n\n return (\n \n \n \n );\n};\n\nLogo.craft = {\n displayName: 'Logo',\n props: { ...BuilderImageDefaultProps, width: '50%', height: '50%', flexShrink: 0 },\n related: { settings: BuilderImageSettings },\n rules: { canMoveIn: () => false }\n};\n","import { Resizer } from '../../BuilderComponents/Resizer';\nimport { BuilderImageDefaultProps, BuilderImageSettings, ImageAndVideoProps } from './ImageAndVideo.settings';\n\nexport const Image = (props: ImageAndVideoProps) => {\n const { src, ...rest } = props;\n\n return (\n \n \n \n );\n};\n\nImage.craft = {\n displayName: 'Image',\n props: { ...BuilderImageDefaultProps, flexShrink: 0 },\n related: { settings: BuilderImageSettings },\n rules: { canMoveIn: () => false }\n};\n","import YouTube from 'react-youtube';\n\nimport { Resizer } from '../../BuilderComponents/Resizer';\nimport { BuilderImageDefaultProps, BuilderImageSettings, ImageAndVideoProps } from './ImageAndVideo.settings';\n\nexport const YoutubeVideo = (props: ImageAndVideoProps) => {\n const { youtubeId, ...rest } = props;\n\n return (\n \n {\n e.target.playVideo();\n }}\n />\n \n );\n};\n\nYoutubeVideo.craft = {\n displayName: 'Youtube Video',\n props: { ...BuilderImageDefaultProps, youtubeId: 'oh9SD7PItf4', flexShrink: 0 },\n related: { settings: BuilderImageSettings },\n rules: { canMoveIn: () => false }\n};\n","export const buildMobileNumpadProps = (template: object) => ({\n ...template,\n width: '350px',\n height: '480px',\n outputFontSize: '30px',\n numbersFontSize: '33px',\n deleteFontSize: '24px',\n sendFontSize: '24px'\n});\n\nexport const buildTabletNumpadProps = (template: object) => ({\n ...template,\n width: '360px',\n height: '400px',\n outputFontSize: '30px',\n numbersFontSize: '33px',\n deleteFontSize: '24px',\n sendFontSize: '24px'\n});\n\nexport const defaultTemplate = {\n template: 'Default',\n buttonsBorderRadius: '5px',\n numbersFontFamily: 'Poppins',\n numbersFontWeight: '500',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '50px',\n numbersTextColor: 'rgba(255,255,255,1)',\n numbersBackgroundColor: 'rgba(157,169,178,1)',\n deleteFontFamily: 'Poppins',\n deleteFontWeight: '500',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '45px',\n deleteTextColor: 'rgba(255,255,255,1)',\n deleteBackgroundColor: 'rgba(247,217,0,1)',\n sendFontFamily: 'Poppins',\n sendFontWeight: '500',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '45px',\n sendTextColor: 'rgba(255,255,255,1)',\n sendBackgroundColor: 'rgba(0,204,0,1)',\n containerBackgroundColor: 'rgba(30,41,59,0)',\n containerBorderColor: 'rgba(0,0,0,0)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Poppins',\n outputFontWeight: '500',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: 'rgba(85,85,85,1)',\n outputBackgroundColor: 'rgba(248,250,252,1)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(140,249,255,1) 0px 0px 0px 0px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const greenTemplate = {\n template: 'Green',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: 'rgba(252,252,252,1)',\n numbersBackgroundColor: 'rgba(24,159,44,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(250,111,111,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,255,56,1)',\n containerBackgroundColor: 'rgba(97,240,116,0.54)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(150,236,158,1) 0px 0px 10px 10px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const blackWhiteTemplate = {\n template: 'Black and white',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#1e293b',\n numbersBackgroundColor: 'rgba(248,250,252,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(250,111,111,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,255,56,1)',\n containerBackgroundColor: 'rgba(30,41,59,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(140,249,255,1) 0px 0px 0px 0px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const blackLtBlueTemplate = {\n template: 'Black and light blue',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#1e293b',\n numbersBackgroundColor: 'rgba(149,238,254,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(250,111,111,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,255,56,1)',\n containerBackgroundColor: 'rgba(30,41,59,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(140,249,255,1) 0px 0px 10px 10px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const blackYellowTemplate = {\n template: 'Black and yellow',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#1e293b',\n numbersBackgroundColor: 'rgba(249,254,149,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(250,111,111,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(142,255,56,1)',\n containerBackgroundColor: 'rgba(30,41,59,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(249,254,149,1) 0px 0px 10px 10px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const blackPinkTemplate = {\n template: 'Black and pink',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#1e293b',\n numbersBackgroundColor: 'rgba(255,178,239,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(250,111,111,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(142,255,56,1)',\n containerBackgroundColor: 'rgba(30,41,59,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(255,178,239,1) 0px 0px 10px 10px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const blackRedTemplate = {\n template: 'Black and red',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#1e293b',\n numbersBackgroundColor: 'rgba(239,68,68,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(161,176,255,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(142,255,56,1)',\n containerBackgroundColor: 'rgba(30,41,59,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(239,68,68,1) 0px 0px 10px 10px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const blackGreenTemplate = {\n template: 'Black and green',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#1e293b',\n numbersBackgroundColor: 'rgba(97,255,117,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(250,111,111,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,242,56,1)',\n containerBackgroundColor: 'rgba(30,41,59,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(97,255,117,1) 0px 0px 10px 10px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const blackBlueTemplate = {\n template: 'Black and blue',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#1e293b',\n numbersBackgroundColor: 'rgba(97,192,255,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(250,111,111,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,242,56,1)',\n containerBackgroundColor: 'rgba(30,41,59,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(97,192,255,1) 0px 0px 10px 10px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const blackPurpleTemplate = {\n template: 'Black and purple',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#1e293b',\n numbersBackgroundColor: 'rgba(168,85,247,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(250,111,111,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,242,56,1)',\n containerBackgroundColor: 'rgba(30,41,59,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(168,85,247,1) 0px 0px 10px 10px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const blackOrangeTemplate = {\n template: 'Black and orange',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#1e293b',\n numbersBackgroundColor: 'rgba(251,146,60,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(250,111,111,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,242,56,1)',\n containerBackgroundColor: 'rgba(30,41,59,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(251,146,60,1) 0px 0px 10px 10px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const whiteBlackTemplate = {\n template: 'White and black',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#f8fafc',\n numbersBackgroundColor: 'rgba(30,41,59,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(248,113,113,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,242,56,1)',\n containerBackgroundColor: 'rgba(248,250,252,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(97,255,117,1) 0px 0px 0px 0px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const whiteGreenTemplate = {\n template: 'White and green',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#f8fafc',\n numbersBackgroundColor: 'rgba(74,222,128,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(248,113,113,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,242,56,1)',\n containerBackgroundColor: 'rgba(248,250,252,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(97,255,117,1) 0px 0px 0px 0px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const whiteCyanTemplate = {\n template: 'White and cyan',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#f8fafc',\n numbersBackgroundColor: 'rgba(34,211,238,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(248,113,113,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,242,56,1)',\n containerBackgroundColor: 'rgba(248,250,252,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(97,255,117,1) 0px 0px 0px 0px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const whitePurpleTemplate = {\n template: 'White and purple',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#f8fafc',\n numbersBackgroundColor: 'rgba(192,132,252,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(248,113,113,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,242,56,1)',\n containerBackgroundColor: 'rgba(248,250,252,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(97,255,117,1) 0px 0px 0px 0px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const whiteBlueTemplate = {\n template: 'White and blue',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#f8fafc',\n numbersBackgroundColor: 'rgba(37,99,235,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(248,113,113,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,242,56,1)',\n containerBackgroundColor: 'rgba(248,250,252,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(97,255,117,1) 0px 0px 0px 0px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const whiteOrangeTemplate = {\n template: 'White and orange',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#f8fafc',\n numbersBackgroundColor: 'rgba(253,186,116,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(248,113,113,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,242,56,1)',\n containerBackgroundColor: 'rgba(248,250,252,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(97,255,117,1) 0px 0px 0px 0px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const whitePinkTemplate = {\n template: 'White and pink',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#f8fafc',\n numbersBackgroundColor: 'rgba(252,165,165,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(248,113,113,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,242,56,1)',\n containerBackgroundColor: 'rgba(248,250,252,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(97,255,117,1) 0px 0px 0px 0px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n\nexport const whiteRedTemplate = {\n template: 'White and red',\n buttonsBorderRadius: '30px',\n numbersFontFamily: 'sans-serif',\n numbersFontWeight: 'bold',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '42px',\n numbersTextColor: '#f8fafc',\n numbersBackgroundColor: 'rgba(239,68,68,1)',\n deleteFontFamily: 'Dancing Script',\n deleteFontWeight: 'normal',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '42px',\n deleteTextColor: '#1e293b',\n deleteBackgroundColor: 'rgba(168,85,247,1)',\n sendFontFamily: 'Dancing Script',\n sendFontWeight: 'bold',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '42px',\n sendTextColor: '#1e293b',\n sendBackgroundColor: 'rgba(255,242,56,1)',\n containerBackgroundColor: 'rgba(248,250,252,1)',\n containerBorderColor: 'rgba(0,0,0,0.4)',\n containerBorderWidth: '1px',\n outputFontFamily: 'Verdana',\n outputFontWeight: 'normal',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: '#1e293b',\n outputBackgroundColor: 'rgba(207,207,207,0.51)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(97,255,117,1) 0px 0px 0px 0px',\n width: '571px',\n height: '633px',\n outputVariant: 'large'\n};\n","import { SyntheticEvent, useState } from 'react';\n\nimport { Grid, MenuItem } from '@mui/material';\n\nimport { useNode } from '@craftjs/core';\n\nimport {\n DecorationProps,\n FontStyleOptions,\n FontWeightOptions,\n MarginProps,\n SizeProps,\n TextAlignOptions,\n TypographyCustomPropNames,\n TypographyProps\n} from 'builder/constants/types';\nimport { capitalizeFirstLetter } from 'utils/utils';\n\nimport { useBuilderContext } from '../../../../context/useBuilderContext';\nimport { getCorrectPropsObjectForDevice } from '../../../../utils/getCorrectPropForDevice';\nimport { SettingsAccordionItem } from '../../BuilderComponents/AccordionItem';\nimport { BuilderFormControl, BuilderInputLabel, BuilderSelect } from '../../BuilderComponents/BuilderMuiComponents';\nimport { BuilderSlider } from '../../BuilderComponents/BuilderSlider';\nimport { CraftColorPicker } from '../../BuilderComponents/ColorPicker';\nimport { DecorationSettings } from '../../Settings/Decoration.settings';\nimport { MarginSettings } from '../../Settings/Margin.settings';\nimport { SizeSettings } from '../../Settings/Size.settings';\nimport { TypographySettings } from '../../Settings/Typography.settings';\nimport {\n blackBlueTemplate,\n blackGreenTemplate,\n blackLtBlueTemplate,\n blackOrangeTemplate,\n blackPinkTemplate,\n blackPurpleTemplate,\n blackRedTemplate,\n blackWhiteTemplate,\n blackYellowTemplate,\n buildMobileNumpadProps,\n buildTabletNumpadProps,\n defaultTemplate,\n greenTemplate,\n whiteBlackTemplate,\n whiteBlueTemplate,\n whiteCyanTemplate,\n whiteGreenTemplate,\n whiteOrangeTemplate,\n whitePinkTemplate,\n whitePurpleTemplate,\n whiteRedTemplate\n} from './templates';\n\nconst templateMap = {\n Default: defaultTemplate,\n Green: greenTemplate,\n 'Black and white': blackWhiteTemplate,\n 'Black and light blue': blackLtBlueTemplate,\n 'Black and blue': blackBlueTemplate,\n 'Black and green': blackGreenTemplate,\n 'Black and orange': blackOrangeTemplate,\n 'Black and pink': blackPinkTemplate,\n 'Black and purple': blackPurpleTemplate,\n 'Black and red': blackRedTemplate,\n 'Black and yellow': blackYellowTemplate,\n 'White and black': whiteBlackTemplate,\n 'White and green': whiteGreenTemplate,\n 'White and cyan': whiteCyanTemplate,\n 'White and purple': whitePurpleTemplate,\n 'White and blue': whiteBlueTemplate,\n 'White and orange': whiteOrangeTemplate,\n 'White and pink': whitePinkTemplate,\n 'White and red': whiteRedTemplate\n};\n\nconst availableTemplates: TemplateOptions[] = [\n 'Default',\n 'Black and white',\n 'Black and light blue',\n 'Black and blue',\n 'Black and green',\n 'Black and orange',\n 'Black and pink',\n 'Black and purple',\n 'Black and red',\n 'Black and yellow',\n 'White and black',\n 'White and green',\n 'White and cyan',\n 'White and purple',\n 'White and blue',\n 'White and orange',\n 'White and pink',\n 'White and red',\n 'Green'\n];\n\nconst templateList = availableTemplates.map((temp) => (\n \n {temp}\n \n));\n\nconst typographyOptions: TypographyOptions[] = ['Numbers', 'Delete', 'Send', 'Output'];\n\nconst typographyOptionsList = typographyOptions.map((temp) => (\n \n {temp}\n \n));\n\nconst availableOutputVariants: OutputVariants[] = ['small', 'medium', 'large'];\n\nconst outputList = availableOutputVariants.map((variant) => (\n \n {capitalizeFirstLetter(variant)}\n \n));\n\nexport const NumberPadSettings = () => {\n const {\n actions: { setProp },\n template,\n buttonsBorderRadius,\n numbersFontFamily,\n numbersFontWeight,\n numbersFontStyle,\n numbersTextAlign,\n numbersFontSize,\n numbersTextColor,\n numbersBackgroundColor,\n deleteFontFamily,\n deleteFontWeight,\n deleteFontStyle,\n deleteTextAlign,\n deleteFontSize,\n deleteTextColor,\n deleteBackgroundColor,\n sendFontFamily,\n sendFontWeight,\n sendFontStyle,\n sendTextAlign,\n sendFontSize,\n sendTextColor,\n sendBackgroundColor,\n containerBackgroundColor,\n containerBorderColor,\n containerBorderWidth,\n outputVariant,\n outputFontFamily,\n outputFontWeight,\n outputFontStyle,\n outputTextAlign,\n outputFontSize,\n outputTextColor,\n outputBackgroundColor,\n marginBottom,\n marginTop,\n marginLeft,\n marginRight,\n borderRadius,\n boxShadow,\n width,\n height\n } = useNode((node) => ({\n template: node.data.props.template,\n buttonsBorderRadius: node.data.props.buttonsBorderRadius,\n numbersFontFamily: node.data.props.numbersFontFamily,\n numbersFontWeight: node.data.props.numbersFontWeight,\n numbersFontStyle: node.data.props.numbersFontStyle,\n numbersTextAlign: node.data.props.numbersTextAlign,\n numbersFontSize: node.data.props.numbersFontSize,\n numbersTextColor: node.data.props.numbersTextColor,\n numbersBackgroundColor: node.data.props.numbersBackgroundColor,\n deleteFontFamily: node.data.props.deleteFontFamily,\n deleteFontWeight: node.data.props.deleteFontWeight,\n deleteFontStyle: node.data.props.deleteFontStyle,\n deleteTextAlign: node.data.props.deleteTextAlign,\n deleteFontSize: node.data.props.deleteFontSize,\n deleteTextColor: node.data.props.deleteTextColor,\n deleteBackgroundColor: node.data.props.deleteBackgroundColor,\n sendFontFamily: node.data.props.sendFontFamily,\n sendFontWeight: node.data.props.sendFontWeight,\n sendFontStyle: node.data.props.sendFontStyle,\n sendTextAlign: node.data.props.sendTextAlign,\n sendFontSize: node.data.props.sendFontSize,\n sendTextColor: node.data.props.sendTextColor,\n sendBackgroundColor: node.data.props.sendBackgroundColor,\n containerBackgroundColor: node.data.props.containerBackgroundColor,\n containerBorderColor: node.data.props.containerBorderColor,\n containerBorderWidth: node.data.props.containerBorderWidth,\n outputVariant: node.data.props.outputVariant,\n outputFontFamily: node.data.props.outputFontFamily,\n outputFontWeight: node.data.props.outputFontWeight,\n outputFontStyle: node.data.props.outputFontStyle,\n outputTextAlign: node.data.props.outputTextAlign,\n outputFontSize: node.data.props.outputFontSize,\n outputTextColor: node.data.props.outputTextColor,\n outputBackgroundColor: node.data.props.outputBackgroundColor,\n marginBottom: node.data.props.marginBottom,\n marginTop: node.data.props.marginTop,\n marginLeft: node.data.props.marginLeft,\n marginRight: node.data.props.marginRight,\n borderRadius: node.data.props.borderRadius,\n boxShadow: node.data.props.boxShadow,\n width: node.data.props.width,\n height: node.data.props.height\n }));\n\n const { device } = useBuilderContext();\n\n const [expanded, setExpanded] = useState(false);\n\n const [selectedTypographyOption, setSelectedTypographyOption] = useState('Numbers');\n\n const handleAccordionChange = (panel: string) => (_: SyntheticEvent, isExpanded: boolean) => {\n setExpanded(isExpanded ? panel : false);\n };\n\n const updateProp = (prop: any) => (value: any) => {\n setProp((props: any) => {\n props[prop] = value;\n return props;\n });\n };\n\n const decorationProps = {\n borderRadius,\n boxShadow\n } as Required;\n\n const marginProps = {\n marginBottom,\n marginTop,\n marginLeft,\n marginRight\n } as Required;\n\n const sizeProps = {\n width,\n height\n } as Required;\n\n const numbersTypographyProps: RequiredCustomTypographyProps = {\n color: numbersTextColor,\n fontWeight: numbersFontWeight,\n fontStyle: numbersFontStyle,\n textAlign: numbersTextAlign,\n fontSize: numbersFontSize,\n fontFamily: numbersFontFamily,\n\n colorPropName: 'numbersTextColor',\n fontWeightPropName: 'numbersFontWeight',\n fontStylePropName: 'numbersFontStyle',\n textAlignPropName: 'numbersTextAlign',\n fontSizePropName: 'numbersFontSize',\n fontFamilyPropName: 'numbersFontFamily'\n };\n\n const deleteTypographyProps: RequiredCustomTypographyProps = {\n color: deleteTextColor,\n fontWeight: deleteFontWeight,\n fontStyle: deleteFontStyle,\n textAlign: deleteTextAlign,\n fontSize: deleteFontSize,\n fontFamily: deleteFontFamily,\n\n colorPropName: 'deleteTextColor',\n fontWeightPropName: 'deleteFontWeight',\n fontStylePropName: 'deleteFontStyle',\n textAlignPropName: 'deleteTextAlign',\n fontSizePropName: 'deleteFontSize',\n fontFamilyPropName: 'deleteFontFamily'\n };\n\n const sendTypographyProps: RequiredCustomTypographyProps = {\n color: sendTextColor,\n fontWeight: sendFontWeight,\n fontStyle: sendFontStyle,\n textAlign: sendTextAlign,\n fontSize: sendFontSize,\n fontFamily: sendFontFamily,\n\n colorPropName: 'sendTextColor',\n fontWeightPropName: 'sendFontWeight',\n fontStylePropName: 'sendFontStyle',\n textAlignPropName: 'sendTextAlign',\n fontSizePropName: 'sendFontSize',\n fontFamilyPropName: 'sendFontFamily'\n };\n\n const outputTypographyProps: RequiredCustomTypographyProps = {\n color: outputTextColor,\n fontWeight: outputFontWeight,\n fontStyle: outputFontStyle,\n textAlign: outputTextAlign,\n fontSize: outputFontSize,\n fontFamily: outputFontFamily,\n\n colorPropName: 'outputTextColor',\n fontWeightPropName: 'outputFontWeight',\n fontStylePropName: 'outputFontStyle',\n textAlignPropName: 'outputTextAlign',\n fontSizePropName: 'outputFontSize',\n fontFamilyPropName: 'outputFontFamily'\n };\n\n const updateTemplate = (variant: unknown) => {\n setProp((props: any) => {\n Object.entries(\n getCorrectPropsObjectForDevice(device)(\n buildMobileNumpadProps(templateMap[variant as keyof typeof templateMap]),\n buildTabletNumpadProps(templateMap[variant as keyof typeof templateMap]),\n templateMap[variant as keyof typeof templateMap]\n ) as any\n ).forEach(([prop, value]) => {\n props[prop] = value;\n });\n\n return props;\n });\n };\n\n const handleBorderRadiusChange: HandleSliderChange = (_, newValue) => {\n updateProp('buttonsBorderRadius')(`${newValue}px`);\n };\n\n const handleBorderWidthChange: HandleSliderChange = (_, newValue) => {\n updateProp('containerBorderWidth')(`${newValue}px`);\n };\n\n return (\n <>\n \n \n Select template \n {\n updateTemplate(e.target.value);\n }}>\n {templateList}\n \n \n\n \n Select output variant \n {\n updateProp('outputVariant')(e.target.value);\n }}>\n {outputList}\n \n \n\n \n\n \n\n \n \n \n \n \n \n \n \n \n\n \n \n Select target \n {\n setSelectedTypographyOption(e.target.value as TypographyOptions);\n }}>\n {typographyOptionsList}\n \n \n\n {selectedTypographyOption === 'Output' && (\n <>\n \n \n >\n )}\n\n {selectedTypographyOption === 'Numbers' && (\n <>\n \n \n >\n )}\n\n {selectedTypographyOption === 'Delete' && (\n <>\n \n \n >\n )}\n\n {selectedTypographyOption === 'Send' && (\n <>\n \n \n >\n )}\n \n\n \n \n \n\n \n \n \n\n \n \n \n >\n );\n};\n\nexport type TemplateOptions =\n | 'Default'\n | 'Black and white'\n | 'Black and light blue'\n | 'Black and yellow'\n | 'Black and pink'\n | 'Black and red'\n | 'Black and green'\n | 'Black and blue'\n | 'Black and purple'\n | 'Black and orange'\n | 'White and black'\n | 'White and green'\n | 'White and cyan'\n | 'White and purple'\n | 'White and blue'\n | 'White and orange'\n | 'White and pink'\n | 'White and red'\n | 'Green';\n\nexport type TypographyOptions = 'Numbers' | 'Delete' | 'Send' | 'Output';\n\nexport type OutputVariants = 'small' | 'medium' | 'large';\n\ntype HandleSliderChange = (_: Event, newValue: number | number[]) => void;\n\ntype RequiredCustomTypographyProps = Required;\n\nexport interface NumberPadProps extends MarginProps, DecorationProps {\n template?: TemplateOptions;\n\n buttonsBorderRadius?: string;\n\n numbersFontFamily?: string;\n numbersFontWeight?: FontWeightOptions;\n numbersFontStyle?: FontStyleOptions;\n numbersTextAlign?: TextAlignOptions;\n numbersFontSize?: string;\n numbersTextColor?: string;\n numbersBackgroundColor?: string;\n\n deleteFontFamily?: string;\n deleteFontWeight?: FontWeightOptions;\n deleteFontStyle?: FontStyleOptions;\n deleteTextAlign?: TextAlignOptions;\n deleteFontSize?: string;\n deleteTextColor?: string;\n deleteBackgroundColor?: string;\n\n sendFontFamily?: string;\n sendFontWeight?: FontWeightOptions;\n sendFontStyle?: FontStyleOptions;\n sendTextAlign?: TextAlignOptions;\n sendFontSize?: string;\n sendTextColor?: string;\n sendBackgroundColor?: string;\n\n containerBackgroundColor?: string;\n containerBorderColor?: string;\n containerBorderWidth?: string;\n\n outputVariant?: OutputVariants;\n outputFontFamily?: string;\n outputFontWeight?: FontWeightOptions;\n outputFontStyle?: FontStyleOptions;\n outputTextAlign?: TextAlignOptions;\n outputFontSize?: string;\n outputTextColor?: string;\n outputBackgroundColor?: string;\n\n width?: string;\n height?: string;\n}\n\nexport const numberPadDefaultProps: Required = {\n template: 'Default',\n buttonsBorderRadius: '5px',\n numbersFontFamily: 'Poppins',\n numbersFontWeight: '500',\n numbersFontStyle: 'normal',\n numbersTextAlign: 'center',\n numbersFontSize: '50px',\n numbersTextColor: 'rgba(255,255,255,1)',\n numbersBackgroundColor: 'rgba(157,169,178,1)',\n deleteFontFamily: 'Poppins',\n deleteFontWeight: '500',\n deleteFontStyle: 'normal',\n deleteTextAlign: 'center',\n deleteFontSize: '45px',\n deleteTextColor: 'rgba(255,255,255,1)',\n deleteBackgroundColor: 'rgba(247,217,0,1)',\n sendFontFamily: 'Poppins',\n sendFontWeight: '500',\n sendFontStyle: 'normal',\n sendTextAlign: 'center',\n sendFontSize: '45px',\n sendTextColor: 'rgba(255,255,255,1)',\n sendBackgroundColor: 'rgba(0,204,0,1)',\n containerBackgroundColor: 'rgba(30,41,59,0)',\n containerBorderColor: 'rgba(0,0,0,0)',\n containerBorderWidth: '1px',\n outputVariant: 'large',\n outputFontFamily: 'Poppins',\n outputFontWeight: '500',\n outputFontStyle: 'normal',\n outputTextAlign: 'center',\n outputFontSize: '50px',\n outputTextColor: 'rgba(85,85,85,1)',\n outputBackgroundColor: 'rgba(248,250,252,1)',\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n borderRadius: '25px',\n boxShadow: 'rgba(140,249,255,1) 0px 0px 0px 0px',\n width: '30%',\n height: '60%'\n};\n","import { Box, Grid } from '@mui/material';\n\nimport { Resizer } from '../../BuilderComponents/Resizer';\nimport { NumberPadProps, NumberPadSettings, numberPadDefaultProps } from './NumberPad.settings';\n\nexport const Numberpad = (props: NumberPadProps) => {\n const {\n outputVariant,\n containerBorderColor,\n containerBorderWidth,\n buttonsBorderRadius,\n numbersFontFamily,\n numbersFontWeight,\n numbersFontStyle,\n numbersTextAlign,\n numbersFontSize,\n numbersTextColor,\n numbersBackgroundColor,\n deleteFontFamily,\n deleteFontWeight,\n deleteFontStyle,\n deleteTextAlign,\n deleteFontSize,\n deleteTextColor,\n deleteBackgroundColor,\n sendFontFamily,\n sendFontWeight,\n sendFontStyle,\n sendTextAlign,\n sendFontSize,\n sendTextColor,\n sendBackgroundColor,\n containerBackgroundColor,\n outputFontFamily,\n outputFontWeight,\n outputFontStyle,\n outputTextAlign,\n outputFontSize,\n outputTextColor,\n outputBackgroundColor,\n marginBottom,\n marginTop,\n marginLeft,\n marginRight,\n borderRadius,\n boxShadow\n } = props;\n\n const outputOptions = {\n small: '50%',\n medium: '75%',\n large: '100%'\n };\n const centerCSS = {\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center'\n };\n const buttonCSS = {\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n width: '100%',\n height: '100%',\n borderRadius: buttonsBorderRadius,\n transition: 'all .3s',\n WebkitFontSmoothing: 'subpixel-antialiased',\n '&:active': {\n opacity: 0.7,\n transform: 'translateY(-2px) translateZ(0)',\n boxShadow: '0 5px 10px rgba(0, 0, 0, 0.2)',\n backfaceVisibility: 'hidden',\n WebkitFontSmoothing: 'subpixel-antialiased'\n }\n };\n const numbersCSS = {\n ...buttonCSS,\n fontFamily: numbersFontFamily,\n fontStyle: numbersFontStyle,\n textAlign: numbersTextAlign,\n fontSize: numbersFontSize,\n fontWeight: numbersFontWeight,\n color: numbersTextColor,\n backgroundColor: numbersBackgroundColor\n };\n const deleteCSS = {\n ...buttonCSS,\n fontFamily: deleteFontFamily,\n fontStyle: deleteFontStyle,\n textAlign: deleteTextAlign,\n fontSize: deleteFontSize,\n fontWeight: deleteFontWeight,\n color: deleteTextColor,\n backgroundColor: deleteBackgroundColor\n };\n const sendCSS = {\n ...buttonCSS,\n fontFamily: sendFontFamily,\n fontStyle: sendFontStyle,\n textAlign: sendTextAlign,\n fontSize: sendFontSize,\n fontWeight: sendFontWeight,\n color: sendTextColor,\n backgroundColor: sendBackgroundColor\n };\n const outputCSS = {\n fontFamily: outputFontFamily,\n fontStyle: outputFontStyle,\n textAlign: outputTextAlign,\n fontSize: outputFontSize,\n fontWeight: outputFontWeight,\n color: outputTextColor,\n backgroundColor: outputBackgroundColor,\n width: '100%',\n height: outputOptions[outputVariant as keyof typeof outputOptions]\n };\n\n const renderButtons = () => {\n const buttons = [];\n\n for (let i = 1; i <= 9; i += 1) {\n buttons.push(\n \n {i} \n \n );\n }\n\n buttons.push(\n \n DEL \n \n );\n buttons.push(\n \n 0 \n \n );\n buttons.push(\n \n SKICKA \n \n );\n\n return buttons;\n };\n\n return (\n \n \n \n 0701234567 \n \n {renderButtons()}\n \n \n );\n};\n\nexport const NUMPAD_NAME = 'Numpad';\n\nNumberpad.craft = {\n displayName: NUMPAD_NAME,\n props: { ...numberPadDefaultProps, flexShrink: 0 },\n related: { settings: NumberPadSettings },\n rules: { canMoveIn: () => false }\n};\n","import { Resizer } from '../../BuilderComponents/Resizer';\nimport { QRCodeDefaultProps, QRCodeProps, QRCodeSettings } from './QRCode.settings';\n\nexport const QRCode = (props: QRCodeProps) => {\n const { borderRadius, src } = props;\n\n return (\n \n \n \n );\n};\n\nQRCode.craft = {\n displayName: 'Integrated QR Code',\n props: { ...QRCodeDefaultProps, flexShrink: 0 },\n related: { settings: QRCodeSettings },\n rules: { canMoveIn: () => false }\n};\n","import { SyntheticEvent, useState } from 'react';\n\nimport { useNode } from '@craftjs/core';\nimport payattStoreQR from 'assets/images/payattStoreQR.png';\n\nimport { DecorationProps, MarginProps, SizeProps } from 'builder/constants/types';\n\nimport { SettingsAccordionItem } from '../../BuilderComponents/AccordionItem';\nimport { DecorationSettings } from '../../Settings/Decoration.settings';\nimport { MarginSettings } from '../../Settings/Margin.settings';\nimport { SizeSettings } from '../../Settings/Size.settings';\n\nexport interface QRCodeProps extends MarginProps, DecorationProps {\n width?: string;\n height?: string;\n src?: string;\n}\n\nexport const QRCodeDefaultProps: QRCodeProps = {\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n\n borderRadius: '0px',\n\n width: '250px',\n height: '250px',\n boxShadow: '#000000 0px 0px 0px 0px',\n src: payattStoreQR\n};\n\nexport const QRCodeSettings = () => {\n const {\n width,\n height,\n\n marginBottom,\n marginTop,\n marginLeft,\n marginRight,\n\n borderRadius,\n boxShadow,\n\n actions: { setProp }\n } = useNode((node) => ({\n width: node.data.props.width,\n height: node.data.props.height,\n\n marginBottom: node.data.props.marginBottom,\n marginTop: node.data.props.marginTop,\n marginLeft: node.data.props.marginLeft,\n marginRight: node.data.props.marginRight,\n\n borderRadius: node.data.props.borderRadius,\n boxShadow: node.data.props.boxShadow\n }));\n\n const updateProp = (prop: keyof QRCodeProps) => (value: any) => {\n setProp((props: any) => {\n props[prop] = value;\n return props;\n });\n };\n\n const [expanded, setExpanded] = useState(false);\n\n const handleChange = (panel: string) => (_: SyntheticEvent, isExpanded: boolean) => {\n setExpanded(isExpanded ? panel : false);\n };\n\n const marginProps = {\n marginBottom,\n marginTop,\n marginLeft,\n marginRight\n } as Required;\n\n const decorationProps = {\n borderRadius,\n boxShadow\n } as Required;\n\n const sizeProps = {\n width,\n height\n } as Required;\n\n return (\n <>\n \n \n \n\n \n \n \n\n \n \n \n >\n );\n};\n","import { SyntheticEvent, useState } from 'react';\n\nimport { useNode } from '@craftjs/core';\n\nimport { DecorationProps, MarginProps, SizeProps } from 'builder/constants/types';\n\nimport { SettingsAccordionItem } from '../../BuilderComponents/AccordionItem';\nimport { BuilderTextField } from '../../BuilderComponents/BuilderMuiComponents';\nimport { DecorationSettings } from '../../Settings/Decoration.settings';\nimport { MarginSettings } from '../../Settings/Margin.settings';\nimport { SizeSettings } from '../../Settings/Size.settings';\n\nexport interface QRCodeUserProps extends MarginProps, DecorationProps {\n width?: string;\n height?: string;\n qrCodeUrl?: string;\n}\n\nexport const QRCodeUserDefaultProps: QRCodeUserProps = {\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n\n borderRadius: '0px',\n\n width: '250px',\n height: '250px',\n boxShadow: '#000000 0px 0px 0px 0px',\n\n qrCodeUrl: 'https://payattclub.se'\n};\n\nexport const QRCodeUserSettings = () => {\n const {\n width,\n height,\n\n marginBottom,\n marginTop,\n marginLeft,\n marginRight,\n\n borderRadius,\n boxShadow,\n\n qrCodeUrl,\n\n actions: { setProp }\n } = useNode((node) => ({\n width: node.data.props.width,\n height: node.data.props.height,\n\n marginBottom: node.data.props.marginBottom,\n marginTop: node.data.props.marginTop,\n marginLeft: node.data.props.marginLeft,\n marginRight: node.data.props.marginRight,\n\n borderRadius: node.data.props.borderRadius,\n boxShadow: node.data.props.boxShadow,\n\n qrCodeUrl: node.data.props.qrCodeUrl\n }));\n\n const updateProp = (prop: keyof QRCodeUserProps) => (value: any) => {\n setProp((props: any) => {\n props[prop] = value;\n return props;\n });\n };\n\n const [expanded, setExpanded] = useState(false);\n\n const handleChange = (panel: string) => (_: SyntheticEvent, isExpanded: boolean) => {\n setExpanded(isExpanded ? panel : false);\n };\n\n const marginProps = {\n marginBottom,\n marginTop,\n marginLeft,\n marginRight\n } as Required;\n\n const decorationProps = {\n borderRadius,\n boxShadow\n } as Required;\n\n const sizeProps = {\n width,\n height\n } as Required;\n\n const onBlur = (e: React.FocusEvent) => updateProp('qrCodeUrl')(e.target.value);\n\n const onEnterPress = (\n e: React.KeyboardEvent & {\n target: { value: string };\n }\n ) => {\n if (e.key === 'Enter') updateProp('qrCodeUrl')(e.target.value);\n };\n\n const urlRegex =\n /https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;\n\n const error = !urlRegex.test(qrCodeUrl as string);\n\n return (\n <>\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n >\n );\n};\n","import QRcode from 'react-qr-code';\n\nimport { Resizer } from '../../BuilderComponents/Resizer';\nimport { QRCodeUserDefaultProps, QRCodeUserProps, QRCodeUserSettings } from './QRCodeUser.settings';\n\nconst payattStoreQR =\n 'https://s3.eu-north-1.amazonaws.com/payatt-registration-display-development/62a615dd29e97ec04043b0a6/payattStoreQR-d2decf42-7261-9d32-4138-3b54cc37dc3e.png';\n\nexport const QRCodeUser = (props: QRCodeUserProps) => {\n const { qrCodeUrl, borderRadius } = props;\n\n return (\n \n \n \n );\n};\n\nQRCodeUser.craft = {\n displayName: 'URL QR Code',\n props: { ...QRCodeUserDefaultProps, flexShrink: 0 },\n related: { settings: QRCodeUserSettings },\n rules: { canMoveIn: () => false }\n};\n","import { useNode } from '@craftjs/core';\n\nimport { TextDefaultProps, TextProps, TextSettings } from './Text/Text.settings';\n\nexport const PayattDisclaimer = (props: TextProps) => {\n const {\n connectors: { connect, drag }\n } = useNode();\n\n const { text, ...CSS } = props;\n\n return (\n connect(drag(ref as HTMLDivElement))}>\n {text}\n
\n );\n};\n\nexport const payattDisclaimerDefaultProps = {\n ...TextDefaultProps,\n fontSize: '24px',\n fontFamily: 'Poppins',\n text: 'Genom att registrera ditt telefonnummer accepterar du PayAtts allmänna villkor och integritetspolicy. För att ta del av dessa i sin helhet besök payattclub.se/se/tac/ och payattclub.se/se/privacy-policy/'\n};\n\nPayattDisclaimer.craft = {\n displayName: 'Payatt Disclaimer',\n props: payattDisclaimerDefaultProps,\n related: { settings: TextSettings }\n};\n","import { SvgIcon } from '@mui/material';\n\nexport const TwoColumns = (\n \n);\n\nexport const TwoRows = (\n \n);\n\nexport const TwoColumnsIcon = () => {\n return {TwoColumns} ;\n};\n\nexport const TwoRowsIcon = () => {\n return {TwoRows} ;\n};\n","import { useMemo } from 'react';\n\nimport {\n Abc,\n CheckBoxOutlineBlank,\n Crop32Outlined,\n Dialpad,\n Filter1,\n Filter2,\n Filter3,\n FormatSize,\n ImageOutlined,\n OndemandVideo,\n PhotoFilter,\n QrCode,\n ShortText,\n TableRowsOutlined,\n ViewCarouselOutlined,\n ViewQuiltOutlined,\n ViewWeekOutlined,\n WindowOutlined\n} from '@mui/icons-material';\n\nimport { regitrationDisplayViewsSelector } from '../../../../../store/selectors';\nimport { store } from '../../../../../store/store';\nimport { useBuilderContext } from '../../../../context/useBuilderContext';\nimport { getCorrectPropForDevice, getCorrectPropsObjectForDevice } from '../../../../utils/getCorrectPropForDevice';\nimport { ContainerCustom2, Logo } from '../../BuilderParts';\nimport { Carousel } from '../../BuilderParts/Carousel/Carousel';\nimport { Container } from '../../BuilderParts/Container/Container';\nimport { Container1x2 } from '../../BuilderParts/Container/Grid/Container1x2';\nimport { Container1x3 } from '../../BuilderParts/Container/Grid/Container1x3';\nimport { Container2x1 } from '../../BuilderParts/Container/Grid/Container2x1';\nimport { Container2x2 } from '../../BuilderParts/Container/Grid/Container2x2';\nimport { Container3x1 } from '../../BuilderParts/Container/Grid/Container3x1';\nimport { ContainerCustom1 } from '../../BuilderParts/Container/Grid/ContainerCustom1';\nimport { Image } from '../../BuilderParts/ImageAndVideo/Image';\nimport { YoutubeVideo } from '../../BuilderParts/ImageAndVideo/YoutubeVideo';\nimport { Numberpad } from '../../BuilderParts/NumberPad/NumberPad';\nimport {\n blackWhiteTemplate,\n buildMobileNumpadProps,\n buildTabletNumpadProps,\n defaultTemplate,\n whiteBlackTemplate\n} from '../../BuilderParts/NumberPad/templates';\nimport { PayattDisclaimer, payattDisclaimerDefaultProps } from '../../BuilderParts/PayattDisclaimer';\nimport { QRCodeUser } from '../../BuilderParts/QRCodeUser/QRCodeUser';\nimport { Text } from '../../BuilderParts/Text/Text';\nimport { TextDefaultProps } from '../../BuilderParts/Text/Text.settings';\nimport { TwoColumnsIcon, TwoRowsIcon } from './svgIcons';\nimport { Component } from './types';\n\nconst useToolboxComponentTree = () => {\n const { device } = useBuilderContext();\n const { selectedViewName } = regitrationDisplayViewsSelector(store.getState());\n\n const textChildrenListOptions = useMemo(() => {\n const textChildren = [\n { name: 'Body', fontSize: getCorrectPropForDevice(device)('16px', '20px', '20px'), id: 'text-body' },\n { name: 'H1', fontSize: getCorrectPropForDevice(device)('30px', '40px', '50px'), id: 'text-h1' },\n { name: 'H2', fontSize: getCorrectPropForDevice(device)('25px', '30px', '40px'), id: 'text-h2' },\n { name: 'H3', fontSize: getCorrectPropForDevice(device)('20px', '25px', '30px'), id: 'text-h3' },\n { name: 'H4', fontSize: getCorrectPropForDevice(device)('18px', '22px', '22px'), id: 'text-h4' }\n ];\n\n return textChildren.map(({ name, fontSize, id }) => ({\n name,\n element: Text,\n Icon: FormatSize,\n id,\n props: { ...TextDefaultProps, fontSize }\n }));\n }, [device]);\n\n const TOOLBOX_COMPONENT_TREE: Component[] = [\n {\n name: 'Container',\n Icon: CheckBoxOutlineBlank,\n id: 'container-selector',\n children: [\n {\n name: 'Default',\n element: Container,\n Icon: Crop32Outlined,\n id: 'default-container'\n },\n {\n name: '1x2',\n element: Container1x2,\n Icon: TwoColumnsIcon,\n id: 'two-columns-container'\n },\n {\n name: '1x3',\n element: Container1x3,\n Icon: ViewWeekOutlined,\n id: 'three-columns-container'\n },\n {\n name: '2x1',\n element: Container2x1,\n Icon: TwoRowsIcon,\n id: 'two-rows-container'\n },\n {\n name: '3x1',\n element: Container3x1,\n Icon: TableRowsOutlined,\n id: 'three-rows-container'\n },\n {\n name: '2x2',\n element: Container2x2,\n Icon: WindowOutlined,\n id: 'two-by-two-container'\n },\n {\n name: 'Custom 1',\n element: ContainerCustom1,\n Icon: ViewQuiltOutlined,\n id: 'custom1-container'\n },\n {\n name: 'Custom 2',\n element: ContainerCustom2,\n Icon: ViewQuiltOutlined,\n id: 'custom2-container'\n }\n ]\n },\n {\n name: 'Text',\n Icon: Abc,\n id: 'text-selector',\n children: textChildrenListOptions\n }\n ];\n\n if (selectedViewName === 'numPadView') {\n TOOLBOX_COMPONENT_TREE.push({\n name: 'Numpad',\n Icon: Dialpad,\n id: 'numpad-selector',\n children: [\n {\n name: 'Default Numpad',\n element: Numberpad,\n Icon: Filter1,\n id: 'default-numpad',\n props: getCorrectPropsObjectForDevice(device)(\n buildMobileNumpadProps(defaultTemplate),\n buildTabletNumpadProps(defaultTemplate),\n defaultTemplate\n )\n },\n {\n name: 'White Numpad',\n element: Numberpad,\n Icon: Filter2,\n id: 'white-numpad',\n props: getCorrectPropsObjectForDevice(device)(\n buildMobileNumpadProps(whiteBlackTemplate),\n buildTabletNumpadProps(whiteBlackTemplate),\n whiteBlackTemplate\n )\n },\n {\n name: 'Black Numpad',\n element: Numberpad,\n Icon: Filter3,\n id: 'black-numpad',\n props: getCorrectPropsObjectForDevice(device)(\n buildMobileNumpadProps(blackWhiteTemplate),\n buildTabletNumpadProps(blackWhiteTemplate),\n blackWhiteTemplate\n )\n }\n ]\n });\n }\n\n const rest: Component[] = [\n {\n name: 'Carousel',\n element: Carousel,\n Icon: ViewCarouselOutlined,\n id: 'default-carousel'\n },\n {\n name: 'Image',\n element: Image,\n Icon: ImageOutlined,\n id: 'default-image'\n },\n {\n name: 'Logo',\n element: Logo,\n Icon: PhotoFilter,\n id: 'default-logo'\n },\n {\n name: 'Generate QR Code',\n element: QRCodeUser,\n Icon: QrCode,\n id: 'default-qrcode-user'\n },\n {\n name: 'YouTube Video',\n element: YoutubeVideo,\n Icon: OndemandVideo,\n id: 'default-video'\n },\n {\n name: 'Payatt Disclaimer',\n element: PayattDisclaimer,\n Icon: ShortText,\n id: 'payatt-disclaimer',\n props: {\n ...payattDisclaimerDefaultProps,\n fontSize: getCorrectPropForDevice(device)('12px', '16px', '20px')\n }\n }\n ];\n\n return [...TOOLBOX_COMPONENT_TREE, ...rest];\n};\n\nexport default useToolboxComponentTree;\n","import { QrCode2 } from '@mui/icons-material';\n\nimport { QRCode } from '../../BuilderParts/QRCode/QRCode';\nimport { Component } from './types';\n\nconst useToolboxIntegratedComponentTree = () => {\n const TOOLBOX_INTEGRATED_COMPONENT_TREE: Component[] = [\n {\n name: 'QR Code',\n element: QRCode,\n Icon: QrCode2,\n id: 'default-qrcode'\n }\n ];\n\n return TOOLBOX_INTEGRATED_COMPONENT_TREE;\n};\n\nexport default useToolboxIntegratedComponentTree;\n","import { SyntheticEvent, useState } from 'react';\n\nimport { useNode } from '@craftjs/core';\n\nimport {\n AlignmentProps,\n BackgroundProps,\n DecorationProps,\n MarginProps,\n PaddingProps,\n SizeProps\n} from 'builder/constants/types';\n\nimport { SettingsAccordionItem } from '../../BuilderComponents/AccordionItem';\nimport { AlignmentSettings } from '../../Settings/Alignment.settings';\nimport { BackgroundSettings } from '../../Settings/Background.settings';\nimport { DecorationSettings } from '../../Settings/Decoration.settings';\nimport { MarginSettings } from '../../Settings/Margin.settings';\nimport { PaddingSettings } from '../../Settings/Padding.settings';\nimport { SizeSettings } from '../../Settings/Size.settings';\n\nexport interface StampCardContainerProps\n extends MarginProps,\n PaddingProps,\n DecorationProps,\n AlignmentProps,\n BackgroundProps {\n width?: string;\n height?: string;\n display?: 'flex' | 'block' | 'inline';\n}\n\nexport const StampCardContainerDefaultProps: StampCardContainerProps = {\n paddingBottom: '0px',\n paddingTop: '0px',\n paddingLeft: '0px',\n paddingRight: '0px',\n\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n\n // borderRadius: '100px',\n\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'space-evenly',\n alignItems: 'center',\n\n width: '80%',\n height: '50%',\n boxShadow: '#000000 0px 0px 0px 0px',\n\n backgroundColor: '#FFFFFF',\n backgroundImage: '',\n backgroundSize: 'cover',\n backgroundPosition: 'center'\n};\n\nexport const StampCardSettings = () => {\n const {\n width,\n height,\n\n paddingBottom,\n paddingTop,\n paddingLeft,\n paddingRight,\n\n marginBottom,\n marginTop,\n marginLeft,\n marginRight,\n\n flexDirection,\n justifyContent,\n alignItems,\n\n borderRadius,\n boxShadow,\n\n backgroundColor,\n backgroundImage,\n backgroundSize,\n backgroundPosition,\n\n actions: { setProp }\n } = useNode((node) => ({\n width: node.data.props.width,\n height: node.data.props.height,\n\n paddingBottom: node.data.props.paddingBottom,\n paddingTop: node.data.props.paddingTop,\n paddingLeft: node.data.props.paddingLeft,\n paddingRight: node.data.props.paddingRight,\n\n marginBottom: node.data.props.marginBottom,\n marginTop: node.data.props.marginTop,\n marginLeft: node.data.props.marginLeft,\n marginRight: node.data.props.marginRight,\n\n flexDirection: node.data.props.flexDirection,\n justifyContent: node.data.props.justifyContent,\n alignItems: node.data.props.alignItems,\n\n borderRadius: node.data.props.borderRadius,\n boxShadow: node.data.props.boxShadow,\n\n backgroundColor: node.data.props.backgroundColor,\n backgroundImage: node.data.props.backgroundImage,\n backgroundSize: node.data.props.backgroundSize,\n backgroundPosition: node.data.props.backgroundPosition\n }));\n\n const updateProp = (prop: keyof StampCardContainerProps) => (value: any) => {\n setProp((props: any) => {\n props[prop] = value;\n return props;\n });\n };\n\n const [expanded, setExpanded] = useState(false);\n\n const handleChange = (panel: string) => (_: SyntheticEvent, isExpanded: boolean) => {\n setExpanded(isExpanded ? panel : false);\n };\n\n const paddingProps = {\n paddingBottom,\n paddingTop,\n paddingLeft,\n paddingRight\n } as Required;\n\n const marginProps = {\n marginBottom,\n marginTop,\n marginLeft,\n marginRight\n } as Required;\n\n const alignmentProps = {\n flexDirection,\n justifyContent,\n alignItems\n } as Required;\n\n const decorationProps = {\n borderRadius,\n boxShadow\n } as Required;\n\n const sizeProps = {\n width,\n height\n } as Required;\n\n const backgroundProps = {\n backgroundImage: backgroundImage?.split('\"')[1],\n backgroundSize,\n backgroundPosition,\n backgroundColor\n } as Required;\n\n return (\n <>\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n >\n );\n};\n","import { useEffect } from 'react';\n\nimport { useEditor, useNode } from '@craftjs/core';\n\nimport { useBuilderContext } from '../../../../context/useBuilderContext';\nimport {\n buildStampCardTextContainerNode,\n buildTextNode,\n useBuildProgressTextNode,\n useBuildRewardTextNode\n} from '../../../../utils/buildNode';\nimport { getCorrectPropForDevice } from '../../../../utils/getCorrectPropForDevice';\nimport { Resizer } from '../../BuilderComponents/Resizer';\nimport { StampCardContainerDefaultProps, StampCardContainerProps, StampCardSettings } from './StampCard.settings';\n\nexport const StampCardNewJoin = (props: StampCardContainerProps) => {\n const { query, actions } = useEditor();\n const { id } = useNode();\n const { device } = useBuilderContext();\n const rewardTextNode = useBuildRewardTextNode();\n const progressTextNode = useBuildProgressTextNode();\n\n const settings = [\n {\n text: 'Welcome to our stamp card campaign! 😍',\n fontSize: getCorrectPropForDevice(device)('26px', '35px', '42px'),\n fontWeight: '500',\n paddingLeft: getCorrectPropForDevice(device)('5px', '0px', '0px'),\n paddingRight: getCorrectPropForDevice(device)('5px', '0px', '0px')\n },\n {\n text: 'Next reward:',\n fontSize: getCorrectPropForDevice(device)('18px', '25px', '33px'),\n fontWeight: 'lighter',\n paddingLeft: getCorrectPropForDevice(device)('5px', '0px', '0px'),\n paddingRight: getCorrectPropForDevice(device)('5px', '0px', '0px')\n }\n ];\n\n useEffect(() => {\n if (!query.node(id).descendants().length) {\n const containerNode = buildStampCardTextContainerNode();\n\n const [headerText, nextRewardText] = settings\n .map(buildTextNode)\n .map((item) => query.parseFreshNode(item).toNode());\n\n const [container, rewardText, progressText] = [containerNode, rewardTextNode, progressTextNode].map(\n (item) => query.parseFreshNode(item).toNode()\n );\n\n actions.history.ignore().add(headerText, id);\n actions.history.ignore().add(container, id);\n actions.history.ignore().add(nextRewardText, container.id);\n actions.history.ignore().add(rewardText, container.id);\n actions.history.ignore().add(progressText, id);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nStampCardNewJoin.craft = {\n displayName: 'Stamp card new join',\n props: { ...StampCardContainerDefaultProps, flexShrink: 0 },\n related: { settings: StampCardSettings }\n};\n","import { useEffect } from 'react';\n\nimport { useEditor, useNode } from '@craftjs/core';\n\nimport { useBuilderContext } from '../../../../context/useBuilderContext';\nimport {\n buildStampCardTextContainerNode,\n buildTextNode,\n useBuildProgressTextNode,\n useBuildRewardTextNode\n} from '../../../../utils/buildNode';\nimport { getCorrectPropForDevice } from '../../../../utils/getCorrectPropForDevice';\nimport { Resizer } from '../../BuilderComponents/Resizer';\nimport { StampCardContainerDefaultProps, StampCardContainerProps, StampCardSettings } from './StampCard.settings';\n\nexport const StampCardProgress = (props: StampCardContainerProps) => {\n const { query, actions } = useEditor();\n const { id } = useNode();\n const { device } = useBuilderContext();\n const rewardTextNode = useBuildRewardTextNode();\n const progressTextNode = useBuildProgressTextNode();\n\n const settings = [\n {\n text: 'Stamp received! 🎉',\n fontSize: getCorrectPropForDevice(device)('30px', '40px', '50px'),\n fontWeight: '500',\n paddingLeft: getCorrectPropForDevice(device)('5px', '0px', '0px'),\n paddingRight: getCorrectPropForDevice(device)('5px', '0px', '0px')\n },\n {\n text: 'Next reward:',\n fontSize: getCorrectPropForDevice(device)('18px', '25px', '33px'),\n fontWeight: 'lighter',\n paddingLeft: getCorrectPropForDevice(device)('5px', '0px', '0px'),\n paddingRight: getCorrectPropForDevice(device)('5px', '0px', '0px')\n }\n ];\n\n useEffect(() => {\n if (!query.node(id).descendants().length) {\n const containerNode = buildStampCardTextContainerNode();\n\n const [headerText, nextRewardText] = settings\n .map(buildTextNode)\n .map((item) => query.parseFreshNode(item).toNode());\n\n const [container, rewardText, progressText] = [containerNode, rewardTextNode, progressTextNode].map(\n (item) => query.parseFreshNode(item).toNode()\n );\n\n actions.history.ignore().add(headerText, id);\n actions.history.ignore().add(container, id);\n actions.history.ignore().add(nextRewardText, container.id);\n actions.history.ignore().add(rewardText, container.id);\n actions.history.ignore().add(progressText, id);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nStampCardProgress.craft = {\n displayName: 'Stamp card progress',\n props: { ...StampCardContainerDefaultProps, flexShrink: 0 },\n related: { settings: StampCardSettings }\n};\n","import { useEffect } from 'react';\n\nimport { useEditor, useNode } from '@craftjs/core';\n\nimport { useBuilderContext } from '../../../../context/useBuilderContext';\nimport { buildStampCardTextContainerNode, buildTextNode, useBuildRewardTextNode } from '../../../../utils/buildNode';\nimport { getCorrectPropForDevice } from '../../../../utils/getCorrectPropForDevice';\nimport { Resizer } from '../../BuilderComponents/Resizer';\nimport { StampCardContainerDefaultProps, StampCardContainerProps, StampCardSettings } from './StampCard.settings';\n\nexport const StampCardReward = (props: StampCardContainerProps) => {\n const { query, actions } = useEditor();\n const { id } = useNode();\n const { device } = useBuilderContext();\n const rewardTextNode = useBuildRewardTextNode();\n\n const textContainerExtraProps = {\n marginTop: getCorrectPropForDevice(device)('20px', '30px', '40px'),\n borderRadius: getCorrectPropForDevice(device)('0px', '20px', '30px')\n };\n\n const settings = [\n {\n text: '🥳 Congratulations! 🎊',\n fontSize: getCorrectPropForDevice(device)('30px', '40px', '50px'),\n fontWeight: 'lighter',\n paddingLeft: getCorrectPropForDevice(device)('5px', '0px', '0px'),\n paddingRight: getCorrectPropForDevice(device)('5px', '0px', '0px')\n },\n {\n text: 'You have reached your stamp card reward!',\n fontSize: getCorrectPropForDevice(device)('21px', '26px', '33px'),\n fontWeight: 'lighter',\n paddingLeft: getCorrectPropForDevice(device)('5px', '0px', '0px'),\n paddingRight: getCorrectPropForDevice(device)('5px', '0px', '0px')\n }\n ];\n\n useEffect(() => {\n if (!query.node(id).descendants().length) {\n const containerNode = buildStampCardTextContainerNode(textContainerExtraProps);\n\n const [container, rewardText] = [containerNode, rewardTextNode].map((item) =>\n query.parseFreshNode(item).toNode()\n );\n\n const [congratsText, rewardReachedText] = settings\n .map(buildTextNode)\n .map((item) => query.parseFreshNode(item).toNode());\n\n actions.history.ignore().add(container, id);\n actions.history.ignore().add(congratsText, container.id);\n actions.history.ignore().add(rewardReachedText, container.id);\n actions.history.ignore().add(rewardText, id);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nStampCardReward.craft = {\n displayName: 'Stamp card reward',\n props: { ...StampCardContainerDefaultProps, justifyContent: 'flex-start', flexShrink: 0 },\n related: { settings: StampCardSettings }\n};\n","import { ShortText } from '@mui/icons-material';\nimport EmojiEventsIcon from '@mui/icons-material/EmojiEvents';\nimport ListAltIcon from '@mui/icons-material/ListAlt';\nimport LoopIcon from '@mui/icons-material/Loop';\nimport PersonAddIcon from '@mui/icons-material/PersonAdd';\n\nimport { ProgressText } from '../../BuilderParts/StampCard/ProgressText';\nimport { RewardText } from '../../BuilderParts/StampCard/RewardText';\nimport { StampCardNewJoin } from '../../BuilderParts/StampCard/StampCardNewJoin';\nimport { StampCardProgress } from '../../BuilderParts/StampCard/StampCardProgress';\nimport { StampCardReward } from '../../BuilderParts/StampCard/StampCardReward';\nimport { Component } from './types';\n\nconst useToolboxStampCardComponentTree = () => {\n const TOOLBOX_STAMP_CARD_COMPONENT_TREE: Component[] = [\n {\n name: 'Cards',\n Icon: ListAltIcon,\n id: 'card-selector',\n children: [\n {\n name: 'New join',\n element: StampCardNewJoin,\n Icon: PersonAddIcon,\n id: 'stamp-card-new-join'\n },\n {\n name: 'Progress',\n element: StampCardProgress,\n Icon: LoopIcon,\n id: 'stamp-card-progress'\n },\n {\n name: 'Reward',\n element: StampCardReward,\n Icon: EmojiEventsIcon,\n id: 'stamp-card-reward'\n }\n ]\n },\n {\n name: 'Dynamically generated reward text',\n element: RewardText,\n Icon: ShortText,\n id: 'stamp-card-reward-text'\n },\n {\n name: 'Dynamically generated progress text',\n element: ProgressText,\n Icon: ShortText,\n id: 'stamp-card-progress-text'\n }\n ];\n\n return TOOLBOX_STAMP_CARD_COMPONENT_TREE;\n};\n\nexport default useToolboxStampCardComponentTree;\n","import React, { useReducer, useState } from 'react';\n\nimport { Menu } from '@mui/icons-material';\nimport { Box, Divider, Drawer, IconButton, Tooltip, Typography } from '@mui/material';\n\nimport { useEditor } from '@craftjs/core';\n\nimport { DisplayViews } from 'builder/constants/types';\nimport { VerticalCenteredFlexBox, VerticalCenteredList } from 'generalComponents/BoxModifications';\n\nimport { regitrationDisplayViewsSelector } from '../../../../../store/selectors';\nimport { store } from '../../../../../store/store';\nimport { SelectDevice } from './DeviceTypeSelector';\nimport { ItemList } from './Toolbox.ItemList';\nimport { AccordionsState } from './types';\nimport useToolboxComponentTree from './useToolboxComponentTree';\nimport useToolboxIntegratedComponentTree from './useToolboxIntegratedComponentTree';\nimport useToolboxStampCardComponentTree from './useToolboxStampCardComponentTree';\n\nexport const Toolbox: React.FC<{ divider?: boolean }> = ({ children, divider = false }) => {\n const { connectors } = useEditor();\n const { selectedViewName } = regitrationDisplayViewsSelector(store.getState());\n\n const TOOLBOX_COMPONENT_TREE = useToolboxComponentTree();\n const TOOLBOX_INTEGRATED_COMPONENT_TREE = useToolboxIntegratedComponentTree();\n const TOOLBOX_STAMP_CARD_COMPONENT_TREE = useToolboxStampCardComponentTree();\n\n const [open, setOpen] = useState(false);\n\n const toggleDrawer = (event: React.KeyboardEvent | React.MouseEvent) => {\n if (\n event.type === 'keydown' &&\n ((event as React.KeyboardEvent).key === 'Tab' || (event as React.KeyboardEvent).key === 'Shift')\n ) {\n return;\n }\n\n setOpen((c) => !c);\n };\n\n const [accordionsState, updateAccordionsState] = useReducer(\n (curr: AccordionsState, next: Partial) => {\n // If we would want to close all other accordions when opening any of them\n // const accordionKeys = Object.keys(curr) as (keyof AccordionsState)[];\n // accordionKeys.forEach((key) => {curr[key] = false;});\n\n return { ...curr, ...next };\n },\n { Container: false, Text: false }\n );\n\n return (\n <>\n \n \n \n \n \n \n \n \n \n Device type\n \n \n \n \n {children}\n \n\n {divider && }\n\n \n \n General Components\n \n \n\n \n {ItemList({\n components: TOOLBOX_COMPONENT_TREE,\n connectors,\n toggleDrawer,\n accordionsState,\n updateAccordionsState,\n exclude: selectedViewName !== DisplayViews.numPadView ? ['Payatt Disclaimer'] : []\n })}\n \n\n {selectedViewName.includes('stampCard') && (\n <>\n \n \n \n Stamp card components\n \n \n \n {ItemList({\n components: TOOLBOX_STAMP_CARD_COMPONENT_TREE,\n connectors,\n toggleDrawer,\n accordionsState,\n updateAccordionsState\n })}\n \n >\n )}\n\n {selectedViewName === 'numPadView' && (\n <>\n \n \n \n Integrated components\n \n \n \n {ItemList({\n components: TOOLBOX_INTEGRATED_COMPONENT_TREE,\n connectors,\n toggleDrawer,\n accordionsState,\n updateAccordionsState\n })}\n \n >\n )}\n \n \n >\n );\n};\n","import { Translate } from 'react-redux-i18n';\nimport { toast } from 'react-toastify';\n\nimport { Alert, AlertTitle, Dialog, DialogContent, Typography } from '@mui/material';\n\nimport { RollbackDialogProps } from 'builder/components/craft/BuilderLayout/TopBar/props';\nimport { loadView } from 'builder/utils/helpers';\nimport { PayAttDialogActions } from 'generalComponents/Dialog/DialogActions';\nimport { SlideUpTransition } from 'generalComponents/Transitions/SlideUp';\nimport { setregistationDisplayViews } from 'store/features/registationDisplayViews/registationDisplayViewsSlice';\nimport { regitrationDisplayViewsSelector } from 'store/selectors';\nimport { store } from 'store/store';\n\nexport const RollbackDialog: React.FC = ({\n rollbackDialogOpen,\n setRollbackDialogOpen,\n dispatch,\n actions,\n id,\n selectedViewName\n}) => {\n const { builderStartSessionState, viewName } = regitrationDisplayViewsSelector(store.getState());\n\n const handleClose = () => {\n setRollbackDialogOpen(false);\n };\n\n const onSubmit = async () => {\n try {\n loadView({ actions, stateToLoad: builderStartSessionState });\n handleClose();\n toast.success('Rolled back');\n dispatch(\n setregistationDisplayViews({\n id,\n selectedViewValue: builderStartSessionState,\n selectedViewName,\n builderStartSessionState,\n viewName\n })\n );\n } catch (error) {\n toast.error('Something went very wrong while trying to rollback view');\n }\n };\n\n return (\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n }\n />\n \n );\n};\n","import { Translate } from 'react-redux-i18n';\nimport { toast } from 'react-toastify';\n\nimport { Alert, AlertTitle, Dialog, DialogContent, Typography } from '@mui/material';\n\nimport { SaveDialogProps } from 'builder/components/craft/BuilderLayout/TopBar/props';\nimport { encodeAndSaveAST } from 'builder/utils/helpers';\nimport { PayAttDialogActions } from 'generalComponents/Dialog/DialogActions';\nimport { SlideUpTransition } from 'generalComponents/Transitions/SlideUp';\nimport { setregistationDisplayViews } from 'store/features/registationDisplayViews/registationDisplayViewsSlice';\nimport { regitrationDisplayViewsSelector } from 'store/selectors';\nimport { store } from 'store/store';\n\nexport const SaveDialog: React.FC = ({\n saveDialogOpen,\n setSaveDialogOpen,\n dispatch,\n actions,\n id,\n overflow,\n query,\n selectedViewName,\n hasNumpad,\n hasDisclaimer\n}) => {\n const { builderStartSessionState, viewName } = regitrationDisplayViewsSelector(store.getState());\n\n const handleClose = () => {\n setSaveDialogOpen(false);\n };\n\n const onSubmit = async () => {\n try {\n const selectedViewValue = await encodeAndSaveAST({ query, selectedViewName, id, actions });\n toast.success('Saved');\n dispatch(\n setregistationDisplayViews({\n id,\n selectedViewValue,\n selectedViewName,\n builderStartSessionState,\n viewName\n })\n );\n handleClose();\n } catch (error) {\n toast.error('Something went very wrong while saving view');\n }\n };\n\n const disabledSave = overflow || hasDisclaimer === false;\n\n return (\n \n \n \n \n \n \n \n\n {hasNumpad === false && (\n \n \n \n )}\n\n {hasDisclaimer === false && (\n \n \n \n )}\n\n {overflow && (\n \n \n \n )}\n\n {!disabledSave && (\n \n \n \n )}\n \n \n \n }\n confirmButtonContent={ }\n confirmButtonProps={{ disabled: disabledSave }}\n />\n \n );\n};\n","import { Translate } from 'react-redux-i18n';\n\nimport { Alert, AlertTitle, Dialog, DialogContent, Typography } from '@mui/material';\n\nimport { ChangeViewDialogProps } from 'builder/components/craft/BuilderLayout/TopBar/props';\nimport { PayAttDialogActions } from 'generalComponents/Dialog/DialogActions';\nimport { SlideUpTransition } from 'generalComponents/Transitions/SlideUp';\n\nexport const ChangeViewDialog: React.FC = ({\n dialogOpen,\n setDialogOpen,\n setConfirmed,\n selectedViewName\n}) => {\n const handleClose = () => {\n setDialogOpen(false);\n };\n\n const confirmSwitchView = () => {\n setDialogOpen(false);\n setConfirmed(selectedViewName);\n };\n\n return (\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n }\n confirmButtonContent={ }\n />\n \n );\n};\n","import { Translate } from 'react-redux-i18n';\n\nimport { Alert, AlertTitle, Dialog, DialogContent, Typography } from '@mui/material';\n\nimport { QuitDialogProps } from 'builder/components/craft/BuilderLayout/TopBar/props';\nimport { PayAttDialogActions } from 'generalComponents/Dialog/DialogActions';\nimport { SlideUpTransition } from 'generalComponents/Transitions/SlideUp';\n\nexport const QuitDialog: React.FC = ({ quitDialogOpen, setQuitDialogOpen, setQuitConfirmed }) => {\n const handleClose = () => {\n setQuitDialogOpen(false);\n };\n\n const confirmQuit = () => {\n setQuitDialogOpen(false);\n setQuitConfirmed(true);\n };\n\n return (\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n }\n confirmButtonContent={ }\n />\n \n );\n};\n","import { Tooltip, Typography, tooltipClasses } from '@mui/material';\n\nimport { VerticalFlexBox } from 'generalComponents/BoxModifications';\n\nimport { regitrationDisplayViewsSelector } from '../../../../../store/selectors';\nimport { store } from '../../../../../store/store';\nimport { viewMap } from '../../../../../views/RegistrationDisplayViews/utils';\n\nexport const SelectedView = ({ zoom }: { zoom: number }) => {\n const { selectedViewName, viewName } = regitrationDisplayViewsSelector(store.getState());\n\n return (\n \n \n {viewName}\n \n \n {viewMap[selectedViewName]}\n \n \n \n Zoom: {Math.floor(zoom)}%\n \n \n \n );\n};\n","import { useCallback, useEffect, useState } from 'react';\nimport { useHotkeys } from 'react-hotkeys-hook';\nimport { I18n } from 'react-redux-i18n';\n\nimport { Close, Redo, Save, Undo, ViewWeekOutlined } from '@mui/icons-material';\nimport HistoryIcon from '@mui/icons-material/History';\nimport { Button, CircularProgress, IconButton, MenuItem, TextField, Tooltip, Typography } from '@mui/material';\nimport AppBar from '@mui/material/AppBar';\nimport Box from '@mui/material/Box';\nimport Toolbar from '@mui/material/Toolbar';\n\nimport { useEditor } from '@craftjs/core';\n\nimport { getDisplayViewCollectionsAPI } from 'api/displayViewColletions/displayViewCollectionsAPI';\nimport { RollbackDialog } from 'builder/components/craft/BuilderLayout/TopBar/RollBackDialog';\nimport { SaveDialog } from 'builder/components/craft/BuilderLayout/TopBar/SaveDialog';\nimport { NUMPAD_NAME } from 'builder/components/craft/BuilderParts';\nimport { DisplayViewCollection, DisplayViews, View } from 'builder/constants/types';\nimport { useBuilderContext } from 'builder/context/useBuilderContext';\nimport { validateIfItemIsInAST } from 'builder/utils/helpers';\nimport { FlexBox, FullDivider } from 'generalComponents/BoxModifications';\nimport { useAsync } from 'generalComponents/hooks/useAsync';\nimport { usePrompt } from 'generalComponents/hooks/useBlocker';\nimport { merchantIsIntegrated } from 'utils/utils';\nimport { viewMap } from 'views/RegistrationDisplayViews/utils';\n\nimport { setregistationDisplayViews } from '../../../../../store/features/registationDisplayViews/registationDisplayViewsSlice';\nimport { merchantSelector, regitrationDisplayViewsSelector } from '../../../../../store/selectors';\nimport { store } from '../../../../../store/store';\nimport { useAppDispatch } from '../../../../../store/store.exports';\nimport { BodyContainerProps } from '../../BuilderParts/BodyContainer/BodyContainer.settings';\nimport { Toolbox } from '../ToolboxDrawer/Toolbox';\nimport { ChangeViewDialog } from './ChangeViewDialog';\nimport { QuitDialog } from './QuitDialog';\nimport { SelectedView } from './TopBar.templateSelector';\nimport { ChangeViewDialogProps } from './props';\n\nconst spinnerCSS = { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)' };\nconst toolbarContainerCSS = {\n display: 'flex',\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between'\n};\n\nconst initialHistoryPointer = -1;\n\nexport const TopBar = () => {\n const { overflow } = useBuilderContext();\n const dispatch = useAppDispatch();\n\n const { selectedViewName, id } = regitrationDisplayViewsSelector(store.getState());\n\n const { data: viewCollection } = useAsync(getDisplayViewCollectionsAPI);\n\n const currentViewCollection = viewCollection?.find((el) => el.id === id);\n\n const {\n canUndo,\n canRedo,\n query,\n actions,\n width: widthProp,\n height: heightProp,\n scale: scaleProp,\n rootNode: rootNodeProp,\n store: {\n history: { pointer: historyPointer }\n }\n } = useEditor((state, q) => {\n const props: BodyContainerProps = state?.nodes?.ROOT?.data?.props;\n\n let width;\n let height;\n let scale;\n let rootNode;\n\n if (props) {\n width = props.width;\n height = props.height;\n scale = props.scale;\n rootNode = state.nodes.ROOT;\n }\n\n return {\n width,\n height,\n scale,\n rootNode,\n canUndo: q.history.canUndo(),\n canRedo: q.history.canRedo()\n };\n });\n\n const undo = () => {\n actions.history.undo();\n };\n\n const redo = () => {\n actions.history.redo();\n };\n\n useHotkeys('meta+z', () => {\n if (canUndo) undo();\n });\n\n useHotkeys('meta+shift+z', () => {\n if (canRedo) redo();\n });\n\n const [rollbackDialogOpen, setRollbackDialogOpen] = useState(false);\n const [saveDialogOpen, setSaveDialogOpen] = useState(false);\n const [quitDialogOpen, setQuitDialogOpen] = useState(false);\n const [quitConfirmed, setQuitConfirmed] = useState(false);\n const [changeViewDialogOpen, setChangeViewDialogOpen] = useState(false);\n const [newView, setNewView] = useState(undefined);\n const [hasNumpad, setHasNumpad] = useState(true);\n const [hasDisclaimer, setHasDisclaimer] = useState(true);\n\n const isIntegrated = merchantIsIntegrated({ merchant: merchantSelector(store.getState()) });\n let possibleViews: View[] = Object.values(DisplayViews);\n\n // Remove integrated view from list\n if (!isIntegrated) possibleViews = possibleViews.slice(1);\n\n const changeView = (e: View) => {\n if (!currentViewCollection) return;\n const hash = currentViewCollection.views[e];\n\n dispatch(\n setregistationDisplayViews({\n id,\n selectedViewValue: hash,\n builderStartSessionState: hash,\n selectedViewName: e,\n viewName: currentViewCollection.name\n })\n );\n };\n\n const handleChangeView = (e: React.ChangeEvent) => {\n if (historyPointer === initialHistoryPointer) return changeView(e.target.value as View);\n\n setNewView(e.target.value as View);\n setChangeViewDialogOpen(true);\n };\n\n const onQuit = useCallback(() => {\n if (historyPointer !== initialHistoryPointer) {\n setQuitDialogOpen(true);\n return;\n }\n\n setQuitConfirmed(true);\n }, [historyPointer]);\n\n useEffect(() => {\n if (!quitConfirmed) return;\n\n dispatch(\n setregistationDisplayViews({\n id: '',\n selectedViewValue: '',\n builderStartSessionState: '',\n viewName: '',\n selectedViewName: DisplayViews.numPadView\n })\n );\n\n window.history.pushState(null, '', window.location.pathname);\n window.location.reload();\n }, [dispatch, quitConfirmed]);\n\n useEffect(() => {\n const onBackButtonEvent = (e: PopStateEvent) => {\n e.preventDefault();\n onQuit();\n };\n\n window.history.pushState(null, '', window.location.pathname);\n window.addEventListener('popstate', onBackButtonEvent);\n\n return () => {\n window.removeEventListener('popstate', onBackButtonEvent);\n };\n }, [onQuit]);\n\n // Alert before leaving the page\n usePrompt(I18n.t('general.leavingThePage'), historyPointer !== initialHistoryPointer && !quitConfirmed);\n\n if (!rootNodeProp || !heightProp || !widthProp) {\n return ;\n }\n\n let zoom = 100;\n\n if (scaleProp) zoom = +scaleProp.toFixed(2) * 100;\n\n const onRollback = () => {\n setRollbackDialogOpen(true);\n };\n\n const onSave = () => {\n if (selectedViewName === DisplayViews.numPadView) {\n setHasNumpad(validateIfItemIsInAST({ itemName: NUMPAD_NAME, query }));\n setHasDisclaimer(validateIfItemIsInAST({ itemName: 'Payatt Disclaimer', query }));\n }\n\n setSaveDialogOpen(true);\n };\n\n const commonProps = {\n dispatch,\n actions,\n id,\n selectedViewName\n };\n\n const rollbackDialogProps = {\n ...commonProps,\n rollbackDialogOpen,\n setRollbackDialogOpen\n };\n\n const saveDialogProps = {\n ...commonProps,\n saveDialogOpen,\n setSaveDialogOpen,\n overflow,\n query\n };\n\n const quitDialogProps = {\n quitDialogOpen,\n setQuitDialogOpen,\n setQuitConfirmed\n };\n\n const changeViewDialogProps: ChangeViewDialogProps = {\n dialogOpen: changeViewDialogOpen,\n setDialogOpen: setChangeViewDialogOpen,\n setConfirmed: (val: View | undefined) => {\n if (val) changeView(val);\n else console.error('No view selected, this should never happen');\n },\n selectedViewName: newView\n };\n\n const displayIconProps = { display: { xs: 'inline-block', sm: 'none' } };\n const displayButtonProps = { display: { xs: 'none', sm: 'inline-block' } };\n\n const saveDisabled = historyPointer === initialHistoryPointer;\n\n return (\n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n null }}>\n svg': { display: 'none' },\n '&.MuiMenuItem-root::before': {\n content: '\"Change View\"'\n }\n }}>\n \n \n \n \n \n {possibleViews.map((el) => {\n return (\n \n {viewMap[el]}\n \n );\n })}\n \n \n\n \n\n \n \n \n \n \n \n \n \n Rollback\n \n \n \n \n\n \n \n \n \n \n \n \n Save\n \n \n \n \n\n \n \n \n \n \n \n \n Quit\n \n \n \n \n \n\n \n \n \n \n \n \n \n );\n};\n","var _g;\nfunction _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\nimport * as React from \"react\";\nfunction SvgPayAttLogoBlack(_ref, svgRef) {\n let {\n title,\n titleId,\n ...props\n } = _ref;\n return /*#__PURE__*/React.createElement(\"svg\", _extends({\n xmlns: \"http://www.w3.org/2000/svg\",\n viewBox: \"0 0 194.57 33.344\",\n ref: svgRef,\n \"aria-labelledby\": titleId\n }, props), title ? /*#__PURE__*/React.createElement(\"title\", {\n id: titleId\n }, title) : null, _g || (_g = /*#__PURE__*/React.createElement(\"g\", {\n id: \"Layer_2\",\n \"data-name\": \"Layer 2\"\n }, /*#__PURE__*/React.createElement(\"g\", {\n id: \"Layer_1-2\",\n \"data-name\": \"Layer 1\"\n }, /*#__PURE__*/React.createElement(\"path\", {\n d: \"M159.735,20.742a3.208,3.208,0,0,1-3.205,3.2h-1.321a3.208,3.208,0,0,1-3.205-3.2V9.385h9.812V6.725H152V1.243h-2.66V6.725h-3.19v2.66h3.19V20.742a5.871,5.871,0,0,0,5.865,5.865h1.321a5.871,5.871,0,0,0,5.866-5.865V17.789h-2.661Z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n d: \"M191.91,17.789v2.953a3.209,3.209,0,0,1-3.206,3.2h-1.32a3.209,3.209,0,0,1-3.206-3.2V9.385h9.812V6.725h-9.812V1.243h-2.66V6.725h-3.189v2.66h3.189V20.742a5.871,5.871,0,0,0,5.866,5.865h1.32a5.872,5.872,0,0,0,5.866-5.865V17.789Z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n d: \"M89.364,6.739l-6.59,17.714L75.836,6.739H72.794L81.5,27.812a6.81,6.81,0,0,1-.4.862,3.29,3.29,0,0,1-.612.781,3.175,3.175,0,0,1-.972.6,6.273,6.273,0,0,1-1.5.382,15.663,15.663,0,0,1-2.2.131v2.777a16.8,16.8,0,0,0,2.638-.179,8.679,8.679,0,0,0,1.923-.511,5.414,5.414,0,0,0,1.392-.809,5.514,5.514,0,0,0,1.013-1.081,7.5,7.5,0,0,0,.751-1.342q.321-.738.633-1.567l.44-1.117,7.837-20Z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n d: \"M50.535,6.185H45.819a5.708,5.708,0,0,0-5.7,5.7h2.66a3.045,3.045,0,0,1,3.042-3.041h4.716a4.343,4.343,0,0,1,4.339,4.339V17.2a2.817,2.817,0,0,0-.56-.61,4.2,4.2,0,0,0-1.2-.651,10.535,10.535,0,0,0-2.046-.5,20.291,20.291,0,0,0-3.113-.2q-1.306,0-2.461.052a12.97,12.97,0,0,0-2.128.26,6.774,6.774,0,0,0-1.733.608,3.846,3.846,0,0,0-1.3,1.111,5.014,5.014,0,0,0-.816,1.762,10.118,10.118,0,0,0-.279,2.561,5.6,5.6,0,0,0,.51,2.483,4.1,4.1,0,0,0,1.611,1.71,8.411,8.411,0,0,0,2.841.99,23.647,23.647,0,0,0,4.2.321,15.533,15.533,0,0,0,3.192-.269,5.245,5.245,0,0,0,1.815-.747,4.078,4.078,0,0,0,1.088-1.1c.16-.234.281-.422.379-.582v2.283h2.66V13.184A7.006,7.006,0,0,0,50.535,6.185ZM54.58,22.467A2.42,2.42,0,0,1,53.574,23.5a5.722,5.722,0,0,1-1.869.617,17.012,17.012,0,0,1-2.889.205q-2.285,0-3.664-.128a6.318,6.318,0,0,1-2.114-.488,1.828,1.828,0,0,1-.972-1,4.887,4.887,0,0,1-.238-1.67,4.76,4.76,0,0,1,.225-1.627,1.62,1.62,0,0,1,.876-.908,5.382,5.382,0,0,1,1.856-.394q1.2-.086,3.161-.085a27.962,27.962,0,0,1,3.29.162,7.666,7.666,0,0,1,2.141.522,2.465,2.465,0,0,1,1.156.934,2.638,2.638,0,0,1,.341,1.349v.1A3.243,3.243,0,0,1,54.58,22.467Z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n d: \"M116.714.01,104.833,26.678h2.912l2.743-6.157h16.2l2.744,6.157h2.912L120.46.01Zm-5.041,17.85,6.914-15.519L125.5,17.86Z\"\n }), /*#__PURE__*/React.createElement(\"path\", {\n d: \"M17.655,0H0V26.678H2.66V15.96l15-.02a5.873,5.873,0,0,0,5.858-5.865V5.865A5.872,5.872,0,0,0,17.655,0Zm3.2,10.075a3.21,3.21,0,0,1-3.2,3.205l-15,.02V2.66h15a3.208,3.208,0,0,1,3.2,3.205Z\"\n })))));\n}\nconst ForwardRef = /*#__PURE__*/React.forwardRef(SvgPayAttLogoBlack);\nexport default __webpack_public_path__ + \"static/media/PayAtt_logo_black.e181f5f5452773e82cc4c48ad5625161.svg\";\nexport { ForwardRef as ReactComponent };","import { Box } from '@mui/material';\n\nimport { UpdateProp } from 'builder/constants/types';\n\nimport { BuilderSlider } from '../../BuilderComponents/BuilderSlider';\n\nexport type Position = 'topRight' | 'topLeft' | 'bottomRight' | 'bottomLeft';\n\ntype PayattLogoType = 'white' | 'black';\n\nexport interface ColorAndPositionSettingsProps {\n type: PayattLogoType;\n position: Position;\n}\n\ninterface Mark {\n value: number;\n label: string;\n prop: Position | PayattLogoType;\n}\n\ntype OnChange = (\n array: Mark[],\n prop: 'type' | 'position',\n fallback: 'black' | 'bottomRight'\n) => (_: Event, value: number | number[]) => void;\n\nexport const ColorAndPositionSettings = ({\n position,\n type,\n updateProp\n}: ColorAndPositionSettingsProps & UpdateProp) => {\n const colorMarks: Mark[] = [\n { value: 0, label: 'Black', prop: 'black' },\n { value: 100, label: 'White', prop: 'white' }\n ];\n\n const positionMarks: Mark[] = [\n { value: 0, label: 'Bottom right', prop: 'bottomRight' },\n { value: 33, label: 'Bottom left', prop: 'bottomLeft' },\n { value: 66, label: 'Top left', prop: 'topLeft' },\n { value: 100, label: 'Top right', prop: 'topRight' }\n ];\n\n const onChange: OnChange = (array, prop, fallback) => (_, value) => {\n const variant = array.find((mark) => mark.value === value);\n updateProp(prop)(variant?.prop || fallback);\n };\n\n const payattLogoColorValue = colorMarks.find((mark) => mark.prop === type)?.value;\n const positionValue = positionMarks.find((mark) => mark.prop === position)?.value;\n\n return (\n \n \n\n \n \n );\n};\n","import { SyntheticEvent, useState } from 'react';\n\nimport { useNode } from '@craftjs/core';\n\nimport { DecorationProps, MarginProps, SizeProps } from 'builder/constants/types';\nimport { useBuilderContext } from 'builder/context/useBuilderContext';\n\nimport { SettingsAccordionItem } from '../../BuilderComponents/AccordionItem';\nimport { SizeSettings } from '../../Settings/Size.settings';\nimport { ColorAndPositionSettings, ColorAndPositionSettingsProps, Position } from './ColorAndPosition.settings';\n\nexport interface PoweredByPayattProps extends MarginProps, DecorationProps {\n width?: string;\n height?: string;\n\n type: 'white' | 'black';\n position: Position;\n}\n\nexport const PoweredByPayattDefaultProps: PoweredByPayattProps = {\n marginBottom: '0px',\n marginTop: '0px',\n marginLeft: '0px',\n marginRight: '0px',\n\n borderRadius: '0px',\n\n width: '200px',\n height: '50px',\n boxShadow: '#000000 0px 0px 0px 0px',\n type: 'black',\n position: 'bottomRight'\n};\n\nexport const PoweredByPayattSettings = () => {\n const { setShouldUpdateIndicator } = useBuilderContext();\n\n const {\n width,\n height,\n position,\n type,\n actions: { setProp },\n store\n } = useNode((node) => ({\n width: node.data.props.width,\n height: node.data.props.height,\n position: node.data.props.position,\n payattLogoColor: node.data.props.payattLogoColor,\n type: node.data.props.type\n }));\n\n const [expanded, setExpanded] = useState(false);\n\n const handleChange = (panel: string) => (_: SyntheticEvent, isExpanded: boolean) => {\n setExpanded(isExpanded ? panel : false);\n };\n\n const updateProp = (prop: keyof PoweredByPayattProps) => (value: any) => {\n setProp((props: any) => {\n props[prop] = value;\n return props;\n });\n\n if (prop === 'position') setShouldUpdateIndicator((c) => c + 1);\n };\n\n const sizeProps = {\n width,\n height\n } as Required;\n\n const colorAndPositionProps = {\n type,\n position\n } as ColorAndPositionSettingsProps;\n\n const rootWidth = store.query.node('ROOT').get().data.props.width;\n\n return (\n <>\n \n \n \n\n \n \n \n >\n );\n};\n","import { useNode } from '@craftjs/core';\nimport PayAttLogoBlack from 'assets/images/PayAtt_logo_black.svg';\nimport PayAttLogoWhite from 'assets/images/PayAtt_logo_white.svg';\n\nimport { NON_DELETABLE_POWERED_BY_PAYATT_NAME } from 'builder/constants/constants';\n\nimport { PoweredByPayattDefaultProps, PoweredByPayattProps, PoweredByPayattSettings } from './PoweredByPayatt.settings';\n\nexport const PoweredByPayatt = (props: PoweredByPayattProps) => {\n const { borderRadius, type, position } = props;\n const {\n connectors: { connect }\n } = useNode();\n\n const positions = {\n topRight: { top: 10, right: 10 },\n topLeft: { top: 10, left: 10 },\n bottomRight: { bottom: 10, right: 10 },\n bottomLeft: { bottom: 10, left: 10 }\n };\n\n const selectedPosition = positions[position as keyof typeof positions];\n\n return (\n \n
\n
\n );\n};\n\nPoweredByPayatt.craft = {\n displayName: NON_DELETABLE_POWERED_BY_PAYATT_NAME,\n props: PoweredByPayattDefaultProps,\n related: { settings: PoweredByPayattSettings },\n rules: { canDrag: () => false, canMoveIn: () => false }\n};\n","import { useEffect, useState } from 'react';\nimport { Navigate } from 'react-router';\n\nimport { Editor, Element, Frame } from '@craftjs/core';\nimport lz from 'lzutf8';\n\nimport CleansSelectionWhenClickedOutside from 'builder/utils/useOutsideSelectionCleanup';\nimport { FlexBox } from 'generalComponents/BoxModifications';\n\nimport { regitrationDisplayViewsSelector } from '../store/selectors';\nimport { store } from '../store/store';\nimport { RenderNode, SettingsPanel, TopBar } from './components/craft/BuilderLayout';\nimport {\n BodyContainer,\n Carousel,\n Container,\n Container1x2,\n Container1x3,\n Container2x1,\n Container2x2,\n Container3x1,\n ContainerCustom1,\n ContainerCustom2,\n Image,\n Logo,\n Numberpad,\n PayattDisclaimer,\n QRCode,\n QRCodeUser,\n Text,\n YoutubeVideo\n} from './components/craft/BuilderParts';\nimport { PoweredByPayatt } from './components/craft/BuilderParts/PoweredByPayatt/PoweredByPayatt';\nimport { PoweredByPayattDefaultProps } from './components/craft/BuilderParts/PoweredByPayatt/PoweredByPayatt.settings';\nimport { ProgressText } from './components/craft/BuilderParts/StampCard/ProgressText';\nimport { RewardText } from './components/craft/BuilderParts/StampCard/RewardText';\nimport { StampCardNewJoin } from './components/craft/BuilderParts/StampCard/StampCardNewJoin';\nimport { StampCardProgress } from './components/craft/BuilderParts/StampCard/StampCardProgress';\nimport { StampCardReward } from './components/craft/BuilderParts/StampCard/StampCardReward';\n\nconst builderContainerCSS = { justifyContent: 'center', gap: 2, marginTop: 1, paddingY: '2px' };\n\nexport const Builder = () => {\n const { selectedViewValue, selectedViewName } = regitrationDisplayViewsSelector(store.getState());\n const [viewValue, setViewValue] = useState(selectedViewValue);\n const [viewName, setViewName] = useState(selectedViewName);\n\n useEffect(() => {\n const unsubscribe = store.subscribe(() => {\n const { selectedViewValue: selectedViewValueInner, selectedViewName: selectedViewNameInner } =\n regitrationDisplayViewsSelector(store.getState());\n\n if (selectedViewNameInner !== selectedViewName) {\n setViewValue(selectedViewValueInner);\n setViewName(selectedViewNameInner);\n }\n });\n\n return unsubscribe;\n }, [selectedViewName]);\n\n if (!selectedViewValue) return ;\n\n const json = lz.decompress(lz.decodeBase64(viewValue));\n\n return (\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n
\n );\n};\n","import 'swiper/swiper-bundle.min.css';\n\nimport { Builder } from 'builder/Builder';\nimport { BuilderProvider } from 'builder/context/builderContext';\n\nimport '../../assets/fonts/Adobe - AGaramondPro-Italic.otf';\nimport '../../assets/scss/builder/carousel.scss';\nimport '../../assets/scss/builder/fonts.scss';\n\nexport const builderRoutes = {\n children: [\n {\n path: '',\n component: (\n \n \n \n )\n }\n ]\n};\n","import { Translate } from 'react-redux-i18n';\nimport { useLocation } from 'react-router-dom';\n\nimport { Box, Link, Typography, TypographyStyle } from '@mui/material';\n\nimport { CenteredFlexBox, FlexBox } from 'generalComponents/BoxModifications';\n\nexport const FOOTER_HEIGTH = 75;\nexport const REDUCED_FOOTER_HEIGHT = 40;\n\nconst LinkStyle = {\n textDecoration: 'none',\n '&:focus, &:hover, &:visited, &:link, &:active': {\n textDecoration: 'none'\n },\n color: 'inherit',\n '&:not(first-child)': {\n pl: '4px'\n },\n '&:not(last-child)': {\n pr: '4px'\n }\n};\n\n// Specify on which routes the footer should be displayed\nconst DisplayRoutes = [\n // '/home', // Added from home view\n '/intellisms/new',\n '/intellisms/overview',\n '/management/profile',\n '/management/settings/merchant',\n '/management/settings/integration',\n '/dashboard',\n '/views',\n '/management/memberships',\n '/stampcard/form',\n '/stampcard/overview',\n '/stampcard/trigger',\n '/contact/form',\n '/faq'\n];\n\nconst DotSeparator = () => • ;\n\nconst Row = ({\n linkText,\n href,\n typographySx,\n newPage = true\n}: {\n linkText: React.ReactNode;\n href: string;\n typographySx: TypographyStyle;\n newPage?: boolean;\n}) => {\n return (\n \n \n \n {linkText}\n \n \n \n );\n};\n\nexport const FooterComponent: React.FC<{ showBackgroundOverlay?: boolean }> = ({ showBackgroundOverlay }) => {\n const typographySx = showBackgroundOverlay ? { color: 'black' } : {};\n\n const flexBoxSx = {\n borderRadius: '0',\n mt: 'auto',\n pt: showBackgroundOverlay ? 0 : '20px',\n backgroundColor: showBackgroundOverlay ? '#ffffff74' : 'transparent',\n minHeight: showBackgroundOverlay ? `${REDUCED_FOOTER_HEIGHT}px` : `${FOOTER_HEIGTH}px`\n };\n\n const data = [\n {\n href: 'https://payattclub.se/tac/',\n linkText: ,\n typographySx\n },\n {\n href: 'https://payattclub.se/privacy-policy/',\n linkText: ,\n typographySx\n },\n {\n href: `/faq`,\n linkText: 'FAQ',\n typographySx,\n newPage: false\n },\n {\n href: 'https://payattclub.se',\n linkText: 'payattclub.se',\n typographySx\n }\n ];\n\n return (\n \n {data.map((el, index) => {\n return (\n \n {Row(el)}\n {index !== data.length - 1 && }\n \n );\n })}\n \n );\n};\n\nconst Footer = (): JSX.Element | null => {\n const location = useLocation();\n\n return DisplayRoutes.includes(location.pathname.toLowerCase()) ? : null;\n};\n\nexport default Footer;\n","import { useEffect, useState } from 'react';\n\nimport { Typography } from '@mui/material';\n\nimport { MerchantInterface } from 'store/features/merchantAndVenues/handlers';\nimport { merchantSelector } from 'store/selectors';\nimport { store } from 'store/store';\n\nconst getTitle = (merchant: MerchantInterface) => {\n // If we are only managing 1 Venue, display the Venue title\n if (merchant.venues.length === 1) {\n return merchant.venues[0].venueTitle;\n }\n return merchant.merchantTitle;\n};\n\nconst MerchantTitle = () => {\n const [merchant, setMerchant] = useState(merchantSelector(store.getState()));\n\n useEffect(() => {\n const unsubscribe = store.subscribe(() => {\n const merchantCurr = merchantSelector(store.getState());\n if (merchant !== merchantCurr) {\n setMerchant(merchantCurr);\n }\n });\n\n return unsubscribe;\n }, [merchant]);\n\n return (\n \n {getTitle(merchant)} \n \n );\n};\n\nexport default MerchantTitle;\n","import { useEffect, useRef, useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\nimport { NavLink } from 'react-router-dom';\n\nimport AccountBoxIcon from '@mui/icons-material/AccountBox';\nimport ExpandMoreTwoToneIcon from '@mui/icons-material/ExpandMoreTwoTone';\nimport LockOpenTwoToneIcon from '@mui/icons-material/LockOpenTwoTone';\nimport { Avatar, Box, Button, Divider, Hidden, List, ListItem, ListItemText, Popover, Typography } from '@mui/material';\nimport { styled } from '@mui/material/styles';\n\nimport { logOut } from 'store/features/auth/actions';\nimport { currentUserSelector } from 'store/selectors';\nimport { store } from 'store/store';\nimport { useAppDispatch } from 'store/store.exports';\nimport { introStepHome } from 'views/Home/JoyrideIntroduction/Main/Home.Joyride.Steps';\n\nconst UserBoxButton = styled(Button)(\n ({ theme }) => `\n padding-left: ${theme.spacing(1)};\n padding-right: ${theme.spacing(1)};\n`\n);\n\nconst MenuUserBox = styled(Box)(\n ({ theme }) => `\n padding: ${theme.spacing(2)};\n`\n);\n\nconst UserBoxText = styled(Box)(\n ({ theme }) => `\n text-align: left;\n padding-left: ${theme.spacing(1)};\n`\n);\n\nconst UserBoxLabel = styled(Typography)(\n ({ theme }) => `\n font-weight: ${theme.typography.fontWeightBold};\n display: block;\n`\n);\n\nconst HeaderUserbox = (): JSX.Element => {\n const dispatch = useAppDispatch();\n const currentUser = currentUserSelector(store.getState());\n\n const [user, setUser] = useState({\n name: currentUser ? currentUser.username : '',\n jobtitle: currentUser ? currentUser.title : ''\n });\n\n const ref = useRef(null);\n const [isOpen, setOpen] = useState(false);\n\n const handleOpen = (): void => {\n setOpen(true);\n };\n\n const handleClose = (): void => {\n setOpen(false);\n };\n\n // Check if we changed the user title\n // Username will trigger logout, so we don't need to check for that\n useEffect(() => {\n const unsubscribe = store.subscribe(() => {\n const currUser = currentUserSelector(store.getState());\n\n // Old venue was {}, but now we have data from DB\n if (currUser && currUser.title !== user.jobtitle) {\n setUser({ ...user, jobtitle: currUser.title });\n }\n });\n return unsubscribe;\n }, [user]);\n\n return (\n <>\n \n \n \n {user.name} \n \n {user.jobtitle}\n \n \n \n \n {user.name ? (\n \n ) : null}\n \n \n \n \n \n \n \n \n {user.name} \n {user.jobtitle} \n \n \n \n \n \n \n } />\n \n
\n \n \n {\n dispatch(logOut());\n }}\n fullWidth>\n \n \n \n \n \n >\n );\n};\n\nexport default HeaderUserbox;\n","import { useEffect, useState } from 'react';\nimport { setLocale } from 'react-redux-i18n';\n\nimport { Box, IconButton } from '@mui/material';\n\nimport { localeSelector } from 'store/selectors';\nimport { store } from 'store/store';\nimport { useAppDispatch } from 'store/store.exports';\nimport Translations from 'translations/translations';\nimport { introStepHome } from 'views/Home/JoyrideIntroduction/Main/Home.Joyride.Steps';\n\nconst GB = '/static/images/flags/GB.png';\nconst SE = '/static/images/flags/SE.png';\n\nconst LanguageSwitcherSelector = () => {\n const dispatch = useAppDispatch();\n const stateStore = store.getState();\n\n const storeLang: string = localeSelector(stateStore);\n\n const [currentLanguage, setCurrentLanguage] = useState(storeLang);\n\n useEffect(() => {\n dispatch(setLocale(currentLanguage));\n }, [currentLanguage, dispatch]);\n\n const options = Object.keys(Translations);\n\n const DropdownStyle = {\n position: 'relative',\n left: 0,\n display: 'none',\n margin: 0,\n padding: 0,\n listStyle: 'none'\n };\n\n const ImageStyle = {\n width: '30px',\n height: '30px',\n '&:hover': {\n width: '40px',\n height: '40px'\n }\n };\n\n const WrapperStyle = {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n margin: 'auto 0',\n mb: '50px',\n '&:hover': {\n '.dropdown': {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center'\n }\n }\n };\n\n return (\n \n \n \n {options.map((lang) =>\n lang !== currentLanguage ? (\n \n setCurrentLanguage(lang)}\n sx={ImageStyle}\n />\n \n ) : null\n )}\n \n \n );\n};\n\nconst HeaderLanguageSelect = () => {\n return (\n \n \n \n );\n};\n\nexport default HeaderLanguageSelect;\n","import { cloneElement, useState } from 'react';\n\nimport CloseIcon from '@mui/icons-material/Close';\nimport { Box, Fade, IconButton, Popper, SxProps, Theme, Typography } from '@mui/material';\nimport { makeStyles } from '@mui/styles';\n\nimport { VerticalCenteredFlexBox } from 'generalComponents/BoxModifications';\nimport LocaleAPI from 'store/locale';\n\nimport { SingleNotificationPopperProps } from './notifications.interfaces';\n\nconst useStyles = makeStyles((theme: Theme) => {\n return {\n popoverRoot: {\n boxShadow: '0 5px 10px rgba(0,0,0,.1)',\n background: 'rgba(255, 255, 255, 1)',\n borderRadius: '16px',\n border: '1px solid rgba(255, 255, 255, 0.3)'\n },\n content: {\n marginTop: 10,\n padding: theme.spacing(2)\n },\n\n // https://github.com/mui/material-ui/blob/master/docs/data/material/components/popper/ScrollPlayground.js\n popper: {\n zIndex: 255555,\n boxShadow: 'none',\n maxHeight: '100%',\n '&[data-popper-placement*=\"left\"] $arrow': {\n right: 0,\n marginRight: '-0.9em',\n height: '3em',\n width: '1em',\n '&::before': {\n borderWidth: '1em 0 1em 1em',\n borderColor: `transparent transparent transparent #bcbaba`\n }\n }\n },\n arrow: {\n position: 'absolute',\n fontSize: 7,\n width: '3em',\n height: '3em',\n '&::before': {\n content: '\"\"',\n margin: 'auto',\n display: 'block',\n width: 0,\n height: 0,\n borderStyle: 'solid'\n }\n }\n };\n});\n\nexport const SingleNotificationPopper: React.FC = ({\n open,\n text,\n children,\n arrowColor,\n closePoppers,\n richText\n}) => {\n const classes = useStyles({ richText });\n\n const [arrowRef, setArrowRef] = useState(null);\n const [childNode, setChildNode] = useState(null);\n\n const locale = LocaleAPI();\n\n const openInCenter = window.innerWidth < 1400;\n\n const hasRichText = richText && richText?.en?.body?.length > 0 && richText?.se?.body?.length > 0;\n\n const popoverRootConditionalStyles: SxProps = openInCenter\n ? { minHeight: '150px' }\n : { maxWidth: !hasRichText ? '400px' : '700px', overflowY: 'auto', maxHeight: '500px' };\n\n return (\n <>\n {cloneElement(children, {\n ...children.props,\n ref: setChildNode\n })}\n \n {({ TransitionProps }) => (\n \n \n {!openInCenter && (\n \n )}\n\n \n \n {text[locale as keyof typeof text].title}\n \n\n {hasRichText && Object.keys(richText).length !== 0 ? (\n \n ) : (\n \n {text[locale as keyof typeof text].body}\n \n )}\n\n theme.palette.grey[500]\n }}>\n \n \n \n \n \n )}\n \n >\n );\n};\n","/* eslint-disable react/display-name */\nimport { Translate } from 'react-redux-i18n';\n\nimport { Box, Button, Divider, Typography } from '@mui/material';\n\nimport { CenteredFlexBox, FlexBox, VerticalFlexBox } from 'generalComponents/BoxModifications';\nimport { dateDifferenceInLocale } from 'utils/utils';\n\nimport { SingleNotificationPopper } from './notification.popper';\nimport { INotificationText, NotificationsCenterItem } from './notifications.interfaces';\n\nconst notificationsSeparator = (\n <>\n \n \n \n >\n);\n\nconst viewButtonProps = {\n sx: {\n mt: 0.5,\n width: '40px',\n height: '30px'\n },\n size: 'small' as const,\n variant: 'outlined' as const,\n color: 'payAttGray' as const\n};\n\nconst glassMorphCSS = (background: string, border: string) => ({\n p: 1.3,\n background,\n borderRadius: '16px',\n backdropFilter: 'blur(10.9px)',\n border: `1px solid ${border}`\n});\n\nconst seenNotificationsCSS = glassMorphCSS('rgba(139, 135, 135, 0.2)', 'rgba(204, 200, 200, 0.5)');\n\nconst unseenNotificationsCSS = glassMorphCSS('rgba(84, 255, 0, 0.25)', 'rgba(84, 255, 0, 0.5)');\n\nconst importantNotificationsCSS = glassMorphCSS('rgba(233, 81, 255, 0.2)', 'rgba(233, 81, 255, 0.5)');\n\nexport const buildNotificationsList =\n ({\n seenNotifications,\n setOpenPopperIndex,\n setPopperText,\n popperText,\n openPopperIndex,\n closePoppers,\n notifications,\n locale\n }: {\n seenNotifications: Set;\n setOpenPopperIndex: React.Dispatch>;\n setPopperText: React.Dispatch<\n React.SetStateAction<{\n en: INotificationText;\n se: INotificationText;\n }>\n >;\n popperText: { en: INotificationText; se: INotificationText };\n openPopperIndex: number;\n closePoppers: () => void;\n notifications: NotificationsCenterItem[];\n locale: string;\n }) =>\n (item: NotificationsCenterItem, index: number) => {\n let notificationCSS = seenNotificationsCSS;\n\n const { viewed, notificationId, important, text, createdAt, richText } = item;\n\n if (!viewed) {\n notificationCSS = important ? importantNotificationsCSS : unseenNotificationsCSS;\n }\n\n const { body, title } = text[locale as keyof typeof text];\n\n const time = dateDifferenceInLocale(new Date(createdAt), locale);\n\n const onClick = () => {\n setOpenPopperIndex(index);\n setPopperText(text);\n seenNotifications.add(notificationId);\n };\n\n let headingColor = '#4b4b4b';\n if (!viewed) headingColor = '#005e0e';\n if (important) headingColor = '#841277';\n\n const headingStyle = {\n color: headingColor,\n marginBottom: '5px'\n };\n\n return (\n \n \n \n \n \n {important ? `! ${title}` : title}\n \n \n {time}\n \n \n \n \n {body}\n \n \n \n \n \n \n {notifications && index !== notifications.length ? notificationsSeparator : null}\n \n \n );\n };\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useSelector } from 'react-redux';\nimport { I18n, Translate } from 'react-redux-i18n';\n\nimport NotificationsActiveTwoToneIcon from '@mui/icons-material/NotificationsActiveTwoTone';\nimport {\n Badge,\n Box,\n Divider,\n IconButton,\n List,\n ListItem,\n Pagination,\n Popover,\n Tooltip,\n Typography\n} from '@mui/material';\n\nimport { getLastFiftyNotificationsAPI, markNotificationsAsSeenAPI } from 'api/notifications/notificationsAPI';\nimport { FlexBox } from 'generalComponents/BoxModifications';\nimport { useAsync } from 'generalComponents/hooks/useAsync';\nimport { setShouldRefreshNotificationCenter } from 'store/features/notifications/notificationsSlice';\nimport LocaleAPI from 'store/locale';\nimport { store } from 'store/store';\nimport { useAppDispatch } from 'store/store.exports';\nimport { introStepHome } from 'views/Home/JoyrideIntroduction/Main/Home.Joyride.Steps';\n\nimport { INotificationText, NotificationsCenterItem } from './notifications.interfaces';\nimport { buildNotificationsList } from './notifications.list';\n\nconst listItemCSS = {\n p: 2,\n minWidth: 350,\n width: 450,\n display: { xs: 'block', sm: 'flex' }\n};\n\nexport const HeaderNotifications = () => {\n const ref = useRef(null);\n\n const [isOpen, setOpen] = useState(false);\n const [page, setPage] = useState(0);\n const [popperText, setPopperText] = useState<{\n en: INotificationText;\n se: INotificationText;\n }>({\n en: { title: '', body: '' },\n se: { title: '', body: '' }\n });\n const [openPopperIndex, setOpenPopperIndex] = useState(-1);\n const [shownNotifications, setShownNotifications] = useState(null);\n\n const { data, execute, loading } = useAsync<{\n notifications: NotificationsCenterItem[];\n }>(getLastFiftyNotificationsAPI);\n\n const notifications = data?.notifications;\n const seenNotifications = useMemo(() => new Set(), []);\n\n const refreshNotifications = async () => {\n // Type 'Set' can only be iterated through when using the '--downlevelIteration' flag or with a '--target' of 'es2015' or higher.ts(2802)\n const notificationIdsWithNoDuplicates: string[] = [];\n seenNotifications.forEach((item) => {\n notificationIdsWithNoDuplicates.push(item);\n });\n\n seenNotifications.clear();\n\n await markNotificationsAsSeenAPI(notificationIdsWithNoDuplicates);\n await execute({});\n };\n\n const closePoppers = useCallback(() => {\n setOpenPopperIndex(-1);\n }, []);\n\n const handleOpen = (): void => {\n setOpen(true);\n closePoppers();\n };\n\n const handleClose = (): void => {\n setOpen(false);\n closePoppers();\n\n if (seenNotifications.size) {\n refreshNotifications();\n }\n };\n\n const locale = LocaleAPI();\n\n useEffect(() => {\n if (notifications) {\n const props = {\n seenNotifications,\n setOpenPopperIndex,\n setPopperText,\n popperText,\n openPopperIndex,\n closePoppers,\n notifications,\n locale\n };\n\n const currentNotificationsPage = notifications\n .slice(page * 5, page * 5 + 5)\n .map(buildNotificationsList(props));\n\n setShownNotifications(currentNotificationsPage);\n }\n }, [closePoppers, notifications, openPopperIndex, page, popperText, seenNotifications, locale]);\n\n const refresh = useSelector(() => store.getState().notifications.shouldRefreshNotificationsCenter);\n const dispatch = useAppDispatch();\n\n if (refresh) {\n execute({});\n dispatch(setShouldRefreshNotificationCenter(false));\n }\n\n const unseenNotificationsCount = notifications && notifications.filter((item) => !item.viewed).length;\n\n const badgeColor = unseenNotificationsCount ? 'primary' : 'action';\n\n const onPageChange = (_: React.ChangeEvent, currPage: number) => {\n setPage(currPage - 1);\n closePoppers();\n\n if (seenNotifications.size) {\n refreshNotifications();\n }\n };\n\n const pagesCount = (notifications && notifications.length > 5 && Math.ceil(notifications.length / 5)) || 1;\n\n const pagination = (\n \n );\n\n return (\n <>\n \n \n \n \n \n \n \n\n \n \n \n \n \n\n {notifications && notifications.length !== 0 && pagination}\n \n\n \n\n \n \n \n {loading || !notifications || notifications.length === 0 ? (\n <>\n {loading && (\n \n \n \n )}\n {!loading && (\n \n \n \n )}\n >\n ) : (\n shownNotifications\n )}\n \n \n
\n \n >\n );\n};\n","import { Box } from '@mui/material';\n\nimport { FlexBox } from 'generalComponents/BoxModifications';\n\nimport HeaderUserbox from '../Userbox/Userbox';\nimport HeaderLanguageSelect from './LanguageSelect/LanguageSelect';\nimport { HeaderNotifications } from './Notifications/notifications-center';\n\nconst HeaderButtons = () => {\n return (\n <>\n \n \n \n \n \n \n \n >\n );\n};\n\nexport default HeaderButtons;\n","import { useContext } from 'react';\n\nimport CloseTwoToneIcon from '@mui/icons-material/CloseTwoTone';\nimport MenuTwoToneIcon from '@mui/icons-material/MenuTwoTone';\nimport { Box, IconButton, Tooltip } from '@mui/material';\nimport { styled } from '@mui/material/styles';\n\nimport 'assets/scss/components/headerMenu/base.scss';\n\nimport { SidebarContext } from 'contexts/SidebarContext';\nimport { FlexBox } from 'generalComponents/BoxModifications';\nimport { introStepHome } from 'views/Home/JoyrideIntroduction/Main/Home.Joyride.Steps';\nimport MerchantTitle from 'views/Navigation/Header/MerchantTitle';\n\nimport HeaderButtons from './Buttons/Buttons';\n\nconst HeaderWrapper = styled(Box)(\n ({ theme }) => `\n color: ${theme.header.textColor};\n box-shadow: ${theme.header.boxShadow};\n`\n);\n\nconst Header = (): JSX.Element => {\n const { sidebarToggle, toggleSidebar } = useContext(SidebarContext);\n\n return (\n \n \n \n \n \n \n {!sidebarToggle ? : }\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default Header;\n","import { Box, Typography, useTheme } from '@mui/material';\n\nimport logoBlack from 'assets/images/PayAtt_logo_black.svg';\nimport logoWhite from 'assets/images/PayAtt_logo_white.svg';\n\nimport { CenteredFlexBox, FlexBox } from './BoxModifications';\n\nexport const MainHomeLogo = () => {\n const theme = useTheme();\n\n return (\n \n \n \n powered by\n \n \n \n \n );\n};\n\nconst LoginLogo = () => {\n return (\n \n \n powered by\n \n \n \n );\n};\n\nexport default LoginLogo;\n","import { FC, ReactNode, useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\nimport { NavLink as RouterLink, matchPath } from 'react-router-dom';\n\nimport ExpandLessTwoToneIcon from '@mui/icons-material/ExpandLessTwoTone';\nimport ExpandMoreTwoToneIcon from '@mui/icons-material/ExpandMoreTwoTone';\nimport { Badge, Button, Collapse, ListItem } from '@mui/material';\n\nimport clsx from 'clsx';\n\nimport { introStepHome } from 'views/Home/JoyrideIntroduction/Main/Home.Joyride.Steps';\n\ninterface SidebarMenuItemProps {\n children?: ReactNode;\n link?: string;\n icon?: any;\n badge?: string;\n open?: boolean;\n active?: boolean;\n name: string;\n}\n\nexport const SidebarMenuItem: FC = ({\n children,\n link,\n icon: Icon,\n badge,\n open: openParent,\n active,\n name\n}) => {\n const [menuToggle, setMenuToggle] = useState(!!openParent);\n\n let homeIntroStepName = '';\n switch (name) {\n case 'sidebar.menu.campaigns.root': {\n homeIntroStepName = introStepHome(9);\n break;\n }\n case 'sidebar.menu.stampCard.root': {\n homeIntroStepName = introStepHome(10);\n break;\n }\n case 'sidebar.menu.views': {\n homeIntroStepName = introStepHome(11);\n break;\n }\n case 'sidebar.menu.dashboard': {\n homeIntroStepName = introStepHome(12);\n break;\n }\n default:\n }\n\n if (children) {\n return (\n \n }\n onClick={() => setMenuToggle(!menuToggle)}\n endIcon={menuToggle ? : }>\n \n \n \n {children}\n \n \n );\n }\n\n return (\n \n }>\n \n {badge && }\n \n \n );\n};\n\nexport const RenderItem = ({\n path,\n name,\n link,\n icon\n}: {\n path: string;\n name: string;\n link: string;\n icon: React.ReactNode;\n}) => {\n const exactMatch = link ? !!matchPath({ path: link, end: true }, path) : false;\n\n return ;\n};\n","import { List, styled } from '@mui/material';\n\nexport const MenuWrapper = styled(List)(\n ({ theme }) => `\n padding: 0;\n margin-bottom: ${theme.spacing(1)};\n \n .MuiListSubheader-root {\n color: #ffffff;\n text-transform: uppercase;\n font-weight: bold;\n line-height: 1.4;\n font-size: ${theme.typography.pxToRem(12)};\n padding: ${theme.spacing(0.5, 2)};\n }\n`\n);\n\nexport const SubMenuWrapper = styled(List)(\n ({ theme }) => `\n padding: 0px;\n\n .MuiList-root .MuiList-root .MuiListItem-root .MuiButton-root {\n font-weight: normal !important;\n }\n \n &.MuiList-root {\n .MuiListItem-root {\n padding: 6px 0px 6px 10px;\n \n .MuiButton-root {\n font-size: ${theme.typography.pxToRem(13)};\n padding-top: ${theme.spacing(0.8)};\n padding-bottom: ${theme.spacing(0.8)};\n color: #cdcdcd;\n background-color: #ffffff00;\n width: 100%;\n justify-content: flex-start;\n position: relative;\n\n .MuiBadge-root {\n position: absolute;\n right: ${theme.spacing(4)};\n\n .MuiBadge-standard {\n font-size: ${theme.typography.pxToRem(9)};\n background: #ffffff00;\n font-weight: bold;\n text-transform: uppercase;\n color: #cdcdcd;\n }\n }\n \n .MuiButton-startIcon,\n .MuiButton-endIcon {\n transition: ${theme.transitions.create(['color'])};\n\n .MuiSvgIcon-root {\n font-size: inherit;\n transition: none;\n }\n }\n\n .MuiButton-startIcon {\n font-size: ${theme.typography.pxToRem(20)};\n margin-right: ${theme.spacing(1)};\n color: #d3d3d3;\n background-color: #bbbbbb00;\n }\n \n .MuiButton-endIcon {\n font-size: ${theme.typography.pxToRem(22)};\n margin-left: auto;\n }\n\n &.Mui-active,\n &:hover {\n background-color: #f2f5f910;\n color: #ffffff;\n\n .MuiButton-startIcon,\n .MuiButton-endIcon {\n color: #ffffff;\n background-color: #f2f5f900;\n }\n }\n }\n\n &.Mui-children {\n flex-direction: column;\n line-height: 1;\n }\n\n .MuiCollapse-root {\n width: 100%;\n\n .MuiList-root {\n padding: ${theme.spacing(0.5, 0)};\n }\n\n .MuiListItem-root {\n padding: 2px ${theme.spacing(0)};\n\n .MuiButton-root {\n font-size: ${theme.typography.pxToRem(12)};\n padding: ${theme.spacing(0.5, 2, 0.5, 6.5)};\n &.Mui-active,\n &:hover {\n background-color: #ffffff00;\n }\n }\n }\n }\n }\n }\n`\n);\n","import { matchPath } from 'react-router-dom';\n\nimport {\n ContactMail,\n Group,\n Insights,\n IntegrationInstructions,\n Message,\n Store,\n TipsAndUpdates\n} from '@mui/icons-material';\nimport SettingsIcon from '@mui/icons-material/Settings';\n\nimport { RenderItem, SidebarMenuItem } from './helpers';\nimport { SubMenuWrapper } from './styled';\n\nconst Dashboard = ({ path }: { path: string }) => {\n return ;\n};\n\nconst MemberManagement = ({ path }: { path: string }) => {\n return ;\n};\n\nconst Settings = ({ path }: { path: string }) => {\n const link = '/management/settings';\n const partialMatch = !!matchPath({ path: link, end: false }, path);\n\n return (\n \n \n \n \n \n \n );\n};\n\nconst Contact = ({ path }: { path: string }) => {\n return ;\n};\n\nexport const Management = ({ path }: { path: string }) => {\n return (\n \n \n \n \n \n \n );\n};\n","import { matchPath } from 'react-router-dom';\n\nimport { CalendarMonth, CardGiftcard, Cast, FiberNew, GridView, Message, SmartScreen } from '@mui/icons-material';\nimport ChatIcon from '@mui/icons-material/Chat';\n\nimport { RenderItem, SidebarMenuItem } from './helpers';\nimport { SubMenuWrapper } from './styled';\n\nconst SMS = ({ path }: { path: string }) => {\n const link = '/IntelliSMS';\n const partialMatch = !!matchPath({ path: link, end: false }, path);\n\n return (\n \n \n \n \n );\n};\n\nconst StampCard = ({ path }: { path: string }) => {\n const link = '/stampcard';\n const partialMatch = !!matchPath({ path: link, end: false }, path);\n\n return (\n \n \n \n \n \n );\n};\n\nconst Views = ({ path }: { path: string }) => {\n return ;\n};\n\nexport const Overview = ({ path }: { path: string }) => {\n return (\n \n \n \n \n \n );\n};\n","import { Translate } from 'react-redux-i18n';\nimport { useLocation } from 'react-router-dom';\n\nimport HomeIcon from '@mui/icons-material/Home';\nimport { ListSubheader } from '@mui/material';\n\nimport { Management } from './ManagementSection';\nimport { Overview } from './OverviewSection';\nimport { RenderItem } from './helpers';\nimport { MenuWrapper, SubMenuWrapper } from './styled';\n\nconst SidebarMenu = (): JSX.Element => {\n const location = useLocation();\n\n return (\n <>\n }>\n \n \n \n \n\n {/* Overview section */}\n \n \n \n }>\n \n \n \n \n\n {/* Management section */}\n \n \n \n }>\n \n \n \n \n >\n );\n};\n\nexport default SidebarMenu;\n","import { useContext } from 'react';\n\nimport { Box, SwipeableDrawer, styled } from '@mui/material';\n\nimport sassVariables from 'assets/scss/export.module.scss';\n\nimport { SidebarContext } from 'contexts/SidebarContext';\nimport { MainHomeLogo } from 'generalComponents/PayAttLogo';\nimport { parseNumberSassVariable } from 'utils/utils';\n\nimport { useHeaderBannersContext } from '../Header/Context/useContextProvider';\nimport SidebarMenu from './SidebarMenu/SidebarMenu';\n\ninterface RootProps {\n numberOfBanners?: number;\n}\n\nconst SwipeableDrawerWrapper = styled(SwipeableDrawer, {\n shouldForwardProp: (prop) => prop !== 'numberOfBanners'\n})(({ numberOfBanners }) => ({\n '.MuiPaper-elevation': {\n // 40 is the size of header badges\n marginTop: `${\n numberOfBanners ? numberOfBanners * parseNumberSassVariable(sassVariables.menuHeaderBannerHeight) : 0\n }px`\n }\n}));\n\nconst SidebarWrapperStyled = styled(Box, {\n shouldForwardProp: (prop) => prop !== 'numberOfBanners'\n})(({ numberOfBanners }) => ({\n height: `calc(100% - ${\n 65 + (numberOfBanners ? numberOfBanners * parseNumberSassVariable(sassVariables.menuHeaderBannerHeight) : 0)\n }px)`,\n overflow: 'hidden'\n}));\n\nconst Sidebar = (): JSX.Element => {\n const { sidebarToggle, toggleSidebar } = useContext(SidebarContext);\n const { banners } = useHeaderBannersContext();\n\n const iOS = typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent);\n\n return (\n \n {\n console.info('On Open Left Sidebar Menu');\n }}\n swipeAreaWidth={20}\n variant=\"persistent\"\n elevation={9}\n transitionDuration={{ enter: 200, exit: 400 }}\n disableBackdropTransition={!iOS}\n disableDiscovery={iOS}>\n theme.sidebar.width,\n color: '#121010',\n background: '#4d4d4d',\n boxShadow: '2px 0 3px rgb(159 162 191 / 18%), 1px 0 1px rgb(159 162 191 / 32%)'\n }}>\n \n \n \n \n theme.sidebar.width,\n backgroundColor: '#ffffff00'\n }}>\n \n \n \n \n \n \n );\n};\n\nexport default Sidebar;\n","import { FC, ReactNode, useContext } from 'react';\nimport { Outlet } from 'react-router-dom';\n\nimport { Box } from '@mui/material';\n\nimport sassVariables from 'assets/scss/export.module.scss';\nimport 'assets/scss/mainPage.scss';\n\nimport { SidebarContext } from 'contexts/SidebarContext';\nimport Footer from 'generalComponents/Footer/Footer';\nimport { parseNumberSassVariable } from 'utils/utils';\n\nimport { useHeaderBannersContext } from './Header/Context/useContextProvider';\nimport Header from './Header/Header';\nimport Sidebar from './Sidebar/Sidebar';\n\ninterface SidebarLayoutProps {\n children?: ReactNode;\n}\n\n// The sidebar creates both left sidebar and header on the top. the MainContent component renders whatever shouold\n// be displayed on the dashboard\nconst SidebarLayout: FC = () => {\n const { sidebarToggle } = useContext(SidebarContext);\n const { banners } = useHeaderBannersContext();\n\n const withoutSidebarStyle = {\n paddingLeft: '0px',\n transform: 'none',\n transition: 'padding-left 300ms cubic-bezier(0.5, 0.15, 0.53, 1) 0ms',\n height: `calc(100% - ${banners.length * parseNumberSassVariable(sassVariables.menuHeaderBannerHeight)}px`\n };\n\n const withSidebarStyle = {\n transform: 'none',\n transition: 'padding-left 200ms cubic-bezier(0, 0, 0.2, 1) 0ms',\n height: `calc(100% - ${banners.length * parseNumberSassVariable(sassVariables.menuHeaderBannerHeight)}px`\n };\n\n return (\n <>\n \n {/* Left sidebar */}\n \n \n {/* Render the main content, (specific View fetched from Routes) */}\n \n \n \n >\n );\n};\n\nexport default SidebarLayout;\n","import { lazy } from 'react';\nimport { Navigate } from 'react-router-dom';\n\nimport { Loader } from 'routes/routes.helpers';\nimport SidebarLayout from 'views/Navigation/Navigation';\n\nconst ContactForm = Loader(lazy(() => import('views/Management/ContactPayAtt/ContactUs')));\n\nexport const contactRoutes = {\n path: '/contact/*',\n component: ,\n children: [\n {\n path: '',\n component: \n },\n {\n path: 'form',\n component: \n }\n ]\n};\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport { Responsive, WidthProvider } from 'react-grid-layout';\n// Grid styling\nimport 'react-grid-layout/css/styles.css';\nimport 'react-resizable/css/styles.css';\n\nimport { Box, useTheme } from '@mui/material';\n\nimport { venuesSelector } from 'store/selectors';\nimport { store } from 'store/store';\nimport {\n GridBreakpointsContextProvider,\n useBreakpointsContext\n} from 'views/Dashboard/Contexts/BreakpointsContext/BreakpointsContext';\nimport { IGlobalGridSettings } from 'views/Dashboard/Contexts/Contexts.interfaces';\nimport { DashboardSelectionProvider } from 'views/Dashboard/Contexts/DashboardSelectionContext/DashboardSelectionContext';\nimport {\n GlobalSettingsContextProvider,\n useGlobalSettingsContext\n} from 'views/Dashboard/Contexts/GlobalSettingsContext/GlobalSettingContext';\nimport { GridItemContextProvider } from 'views/Dashboard/Contexts/GridItemContext/GridItemContext';\nimport { StampCardCampaignStatsContextProvider } from 'views/Dashboard/Contexts/StampCardCampaignStatsContext/StampCardCampaignStatsContext';\nimport { dashboardContainerBreakpoint } from 'views/Dashboard/Dashboard.utils';\nimport { CreateGridItem } from 'views/Dashboard/Grid/ResponsiveGridLayout.CreateGridItem';\nimport { useContainerRect } from 'views/Dashboard/Grid/ResponsiveGridLayout.useContainerRect';\nimport { DashboardBreakpoint } from 'views/Dashboard/Interfaces/Dashboard.breakpoints';\nimport {\n DefaultGridSettings,\n GRID_MARGIN,\n GRID_ROW_HEIGHT,\n RESPONSIVE_COLUMNS\n} from 'views/Dashboard/Interfaces/Dashboard.constants';\nimport { DashboardBreakpointLayouts } from 'views/Dashboard/Interfaces/Dashboard.layout';\nimport { DashboardJoyrideContextProvider } from 'views/Dashboard/JoyrideIntroductions/Dashboard.Joyride.context';\n\nimport { HomeWidgetMenuJoyrideContextProvider } from './JoyrideIntroduction/Settings/Home.JoyrideWidgetMenu.context';\n\nconst ResponsiveGridLayout = WidthProvider(Responsive);\n\nexport const HomepageGrid: React.FC<{ layouts: DashboardBreakpointLayouts }> = ({ layouts }) => {\n const theme = useTheme();\n\n const { globalDragability, globalResizability } = useGlobalSettingsContext();\n\n const [venueIds] = useState(venuesSelector(store.getState())?.map((el) => el.id));\n const { breakpoint, setBreakpoint } = useBreakpointsContext();\n\n const containerRef = useRef(null);\n const containerRect = useContainerRect(containerRef);\n\n useEffect(() => {\n const newBreakpoint: DashboardBreakpoint = dashboardContainerBreakpoint(\n theme.breakpoints.values,\n containerRect.width\n );\n\n if (containerRect.width > 0) setBreakpoint(newBreakpoint);\n }, [containerRect.width, setBreakpoint, theme.breakpoints.values]);\n\n const GridLayout = useMemo(() => {\n if (!breakpoint || containerRect.width < 0) return null;\n\n // Will be recalculated every time the breakpoint changes\n const itemsToShow = layouts[breakpoint].map((gridItemLayout) =>\n CreateGridItem({ gridItemLayout, venueIds, showItemTopBar: false })\n );\n\n return (\n \n {itemsToShow}\n \n );\n\n // We need both breakpoint and containerWidth, to cover all possible scenarios, here are a few examples:\n // 1. First load, the breakpoint is updated after the containerWidth, when it goes from null to value. So we need the breakpoint\n // 2. Toggle sidebar, this will update the container width, but may not change the breakpoint (depends on resolution)\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [breakpoint, layouts, theme.breakpoints.values, venueIds]);\n\n return (\n \n {GridLayout}\n \n );\n};\n\n// The order of the providers is important!\nexport const HomepageGridLayoutWrapper: React.FC<{\n children: React.ReactNode;\n globalGridSettings?: IGlobalGridSettings;\n}> = ({ children, globalGridSettings = DefaultGridSettings }) => (\n \n \n \n \n {/* GridBreakpointsContextProvider, DashboardJoyrideContextProvider and GridItemContextProvider are not used, but needed since there are many components shared with the dashboard. \n E.g. closing a chart from the chart header uses the removeItem function from DashboardSelectionProvider context, but since we hide the close button on the homepage it is OK */}\n \n \n \n {children}\n \n \n \n \n \n \n \n);\n","import { Translate } from 'react-redux-i18n';\n\nimport { Box, Grid, Typography } from '@mui/material';\n\nimport { currentUserSelector } from 'store/selectors';\nimport { store } from 'store/store';\nimport { GlobalSettings } from 'views/Dashboard/GlobalSettings/GlobalSettings';\n\nconst HomepageHeader: React.FC<{ isLoading: boolean }> = ({ isLoading }) => {\n const state = store.getState();\n const currentUser = currentUserSelector(state);\n\n const user = {\n name: currentUser?.contact.firstName\n };\n\n return (\n \n \n \n \n {user.name}!\n \n \n \n \n \n \n\n {!isLoading && }\n \n );\n};\n\nexport default HomepageHeader;\n","import { useEffect, useState } from 'react';\n\nimport { Box } from '@mui/material';\n\nimport { JoyridePayAtt } from 'Joyride/JoyridePayAtt';\nimport { SHOW_HOMEPAGE_INTRO_SEARCH_PARAMS } from 'Joyride/JoyridePayAtt.constants';\n\nimport { introductionSelector } from 'store/selectors';\nimport { store } from 'store/store';\n\nimport { homeIntroSteps, introStepHome } from './Home.Joyride.Steps';\n\nexport const JoyrideIntroductionMain = () => {\n const [show, setShow] = useState(false);\n const [showSkipButton, setShowSkipButton] = useState(false);\n\n useEffect(() => {\n const timer = setTimeout(() => {\n let shouldStart = false;\n\n // Start intro as we were navigated here from Introduction tab\n if (window.location.search.includes(SHOW_HOMEPAGE_INTRO_SEARCH_PARAMS)) {\n shouldStart = true;\n setShowSkipButton(true);\n window.history.replaceState(null, '', window.location.pathname);\n } else {\n shouldStart = introductionSelector(store.getState()).homepage.main;\n }\n\n if (shouldStart) {\n setShow(shouldStart);\n\n setTimeout(() => {\n // Auto scroll the navigation menu to the top to prepare for intro steps\n const sidebarMenu = document.querySelector('.sidebarWrapper .menuWrapper');\n if (sidebarMenu) sidebarMenu.scrollTo(0, 0);\n }, 500);\n }\n }, 2000);\n\n return () => {\n clearTimeout(timer);\n };\n }, []);\n\n if (!show) return null;\n\n return (\n <>\n \n \n \n >\n );\n};\n","import React, { useEffect, useMemo, useState } from 'react';\nimport { Helmet } from 'react-helmet-async';\nimport { I18n } from 'react-redux-i18n';\n\nimport { Box, CircularProgress, Container } from '@mui/material';\n\nimport { VerticalCenteredFlexBox, VerticalFlexBox } from 'generalComponents/BoxModifications';\nimport { FooterComponent, REDUCED_FOOTER_HEIGHT } from 'generalComponents/Footer/Footer';\nimport { BackgroundImages } from 'store/features/dashboard/handlers';\nimport {\n authSelector,\n dashboardBackgroundImageSelector,\n homepageLayoutSelector,\n merchantSelector\n} from 'store/selectors';\nimport { store } from 'store/store';\nimport { useAppDispatch } from 'store/store.exports';\nimport {\n PopulateHomepageLayouts,\n PopulateMerchantAndVenues,\n PopulateStampCardCampaigns,\n PopulateStatistics\n} from 'store/store.populate';\nimport { DashboardBreakpointLayouts } from 'views/Dashboard/Interfaces/Dashboard.layout';\n\nimport { HomepageGrid, HomepageGridLayoutWrapper } from './GridLayout';\nimport HomepageHeader from './HomepageHeader';\nimport { introStepHome } from './JoyrideIntroduction/Main/Home.Joyride.Steps';\nimport { JoyrideIntroductionMain } from './JoyrideIntroduction/Main/Home.JoyrideIntro';\nimport { HOMEPAGE_BACKGROUND_COLOR } from './styles';\n\nconst HomepageGridWithBackgroundImage: React.FC<{ layouts: DashboardBreakpointLayouts; isLoading?: boolean }> = ({\n layouts,\n isLoading\n}) => {\n const [backgroundImage, setBackgroundImage] = useState(dashboardBackgroundImageSelector(store.getState()));\n\n useEffect(() => {\n const unsubscribe = store.subscribe(() => {\n const backgroundImageNew = dashboardBackgroundImageSelector(store.getState());\n if (backgroundImageNew.name !== backgroundImage.name) setBackgroundImage(backgroundImageNew);\n });\n\n return unsubscribe;\n });\n\n const backgroundStyleImage = {\n backgroundImage: `url('${backgroundImage.url}')`,\n backgroundSize: 'cover',\n backgroundRepeat: 'no-repeat'\n };\n\n const backgroundStyleNoImage = { backgroundColor: HOMEPAGE_BACKGROUND_COLOR };\n const backgroundImageStyle = {\n pb: `${REDUCED_FOOTER_HEIGHT}px`,\n mb: `-${REDUCED_FOOTER_HEIGHT}px`,\n flexGrow: 1,\n ...(backgroundImage.name !== BackgroundImages.hide ? backgroundStyleImage : backgroundStyleNoImage)\n };\n\n return (\n <>\n theme.spacing(1, 0, 1, 0) }}>\n \n \n {!isLoading && }\n \n {isLoading ? (\n \n \n \n ) : (\n \n )}\n \n >\n );\n};\n\nconst Home = (): JSX.Element | null => {\n const dispatch = useAppDispatch();\n\n const [fetchingData, setFetchingData] = useState(true);\n\n // Populate redux store with new data every time the page is focused or refreshed\n useEffect(() => {\n const populateStore = async () => {\n const currUser = authSelector(store.getState()).currentUser;\n\n if (currUser) {\n await PopulateMerchantAndVenues(dispatch, currUser);\n const merchantAfterPopulation = merchantSelector(store.getState());\n\n await PopulateHomepageLayouts(dispatch);\n await PopulateStatistics(dispatch, merchantAfterPopulation);\n await PopulateStampCardCampaigns(dispatch, merchantAfterPopulation);\n\n setFetchingData(false);\n }\n };\n\n populateStore();\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const layouts: DashboardBreakpointLayouts = useMemo(() => {\n if (fetchingData) {\n return { xs: [], sm: [], md: [], lg: [], xl: [] };\n }\n\n const homepageLayouts = homepageLayoutSelector(store.getState()).data;\n const xsLayout = homepageLayouts.find((el) => el.breakpoint === 'xs');\n const smLayout = homepageLayouts.find((el) => el.breakpoint === 'sm');\n const mdLayout = homepageLayouts.find((el) => el.breakpoint === 'md');\n const lgLayout = homepageLayouts.find((el) => el.breakpoint === 'lg');\n const xlLayout = homepageLayouts.find((el) => el.breakpoint === 'xl');\n\n const fallBackLayout = lgLayout || mdLayout || xlLayout || smLayout || xsLayout || { layout: [] };\n\n return {\n xs: xsLayout?.layout || fallBackLayout.layout,\n sm: smLayout?.layout || fallBackLayout.layout,\n md: mdLayout?.layout || fallBackLayout.layout,\n lg: lgLayout?.layout || fallBackLayout.layout,\n xl: xlLayout?.layout || fallBackLayout.layout\n };\n }, [fetchingData]);\n\n const gridOrSpinner = fetchingData ? (\n \n \n \n ) : (\n \n \n \n );\n\n return (\n <>\n \n {`${I18n.t('homepage.title')}`} \n \n {gridOrSpinner}\n \n >\n );\n};\n\nexport default Home;\n","export const HOMEPAGE_BACKGROUND_COLOR = '#e6eaec';\n","import { lazy } from 'react';\nimport { Navigate } from 'react-router-dom';\n\nimport { Loader } from 'routes/routes.helpers';\nimport Home from 'views/Home/Home';\nimport { SMSCampaignProvider } from 'views/IntelliSms/context/SMSCampaignContext';\nimport SidebarLayout from 'views/Navigation/Navigation';\n\nconst Registrations = Loader(lazy(() => import('views/Registrations/Registrations')));\n\nconst Profile = Loader(lazy(() => import('views/Profile/Profile')));\n\nexport const homeRoutes = {\n path: '/home/*',\n component: (\n \n \n \n ),\n children: [\n {\n path: '',\n component: \n },\n {\n path: 'registrations',\n component: \n },\n {\n path: 'profile',\n component: \n },\n {\n path: '*',\n component: \n }\n ]\n};\n","import axios from 'axios';\n\nimport { BASE_URL } from 'api/helper';\nimport { handleAxiosError, handleAxiosInvalidResponse } from 'errorHandling/handleResponse';\n\nimport { SmsSuggestionLanguage } from './intelliSms.interfaces';\n\nexport type SetSmsSuggestionResponse = { message: string; status: number; ttl?: number };\n\nexport const getSmsSuggestionLanguageAPI = async (): Promise => {\n try {\n const response = await axios.get(`${BASE_URL}/sms-campaign/sms-suggestion-language`, {\n headers: { 'Content-Type': 'application/json' }\n });\n\n if (response.data && response.status === 200 && response.data.language) {\n return response.data.language;\n }\n\n throw handleAxiosInvalidResponse(response);\n } catch (err) {\n throw handleAxiosError(err);\n }\n};\n\nexport const setSmsSuggestionLanguageAPI = async (\n language: SmsSuggestionLanguage\n): Promise => {\n try {\n const response = await axios.patch(\n `${BASE_URL}/sms-campaign/sms-suggestion-language`,\n { language },\n { headers: { 'Content-Type': 'application/json' } }\n );\n\n if (response.data && response.status === 200 && response.data.message) {\n return { status: response.status, ...response.data };\n }\n\n throw handleAxiosInvalidResponse(response);\n } catch (err: any) {\n if (axios.isAxiosError(err)) {\n if (err.response && err.response.data) {\n return { status: err.response.status, ...err.response.data };\n }\n }\n\n throw handleAxiosError(err);\n }\n};\n","export const smsSuggestionLanguage = {\n Swedish: 'Swedish',\n English: 'English'\n} as const;\n\nexport type SmsSuggestionLanguage = keyof typeof smsSuggestionLanguage;\n","import React from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport CloseIcon from '@mui/icons-material/Close';\nimport { Box, Dialog, DialogContent, DialogContentText, DialogTitle, IconButton, Typography } from '@mui/material';\n\nimport { SmsSuggestionLanguage } from 'api/intelliSms/intelliSms.interfaces';\nimport { PayAttDialogActions } from 'generalComponents/Dialog/DialogActions';\nimport { SlideUpTransition } from 'generalComponents/Transitions/SlideUp';\nimport { venuesSelector } from 'store/selectors';\nimport { store } from 'store/store';\n\ninterface ConfirmChangeLanguageProps {\n open: boolean;\n setOpen: React.Dispatch>;\n onConfirm: () => void;\n language: SmsSuggestionLanguage;\n}\n\nexport const ConfirmChangeLanguage: React.FC = ({ open, setOpen, onConfirm, language }) => {\n const venues = venuesSelector(store.getState());\n\n const handleCloseAccept = () => {\n onConfirm();\n setOpen(false); // Indicate that we accepted with the second 'true'\n };\n\n const handleCloseDecline = () => {\n setOpen(false);\n };\n\n return (\n \n \n \n \n \n \n \n 1 ? 'subtitleMultiVenue' : 'subtitle'\n }`}\n dangerousHTML\n />\n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n","import { Translate } from 'react-redux-i18n';\nimport { toast } from 'react-toastify';\n\nimport { Box } from '@mui/material';\n\nimport { SetSmsSuggestionResponse } from 'api/intelliSms/intelliSms.api';\nimport { PayAttFailToast, PayAttSuccessToast } from 'generalComponents/Toasts/Toasts';\n\nexport const handleResponse = (data: SetSmsSuggestionResponse) => {\n if (data.status === 200) {\n if (data.message.includes('Language already set')) {\n return toast.info(\n \n \n
\n );\n }\n\n return PayAttSuccessToast(\n \n \n
\n );\n }\n\n // This means we failed since there is still a ttl on the previous update, in seconds\n if (data.status === 423 && data.ttl) {\n const remainingMinutes = Math.floor(data.ttl / 60);\n const remainingSeconds = Math.floor(data.ttl % 60);\n\n return toast.error(\n \n {' '}\n \n {remainingMinutes}m {remainingSeconds % 60}s\n \n
\n );\n }\n\n return PayAttFailToast(\n \n \n
\n );\n};\n","import { useEffect, useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Box, Button, MenuItem, Select, SelectChangeEvent, Typography } from '@mui/material';\n\nimport { getSmsSuggestionLanguageAPI, setSmsSuggestionLanguageAPI } from 'api/intelliSms/intelliSms.api';\nimport {\n SmsSuggestionLanguage,\n smsSuggestionLanguage as smsSuggestionLanguageObj\n} from 'api/intelliSms/intelliSms.interfaces';\nimport { FlexBox } from 'generalComponents/BoxModifications';\n\nimport { ConfirmChangeLanguage } from './IntelliSms.ConfirmDialog';\nimport { handleResponse } from './IntelliSms.toasts';\n\nexport const ChangeLanguage = (): JSX.Element => {\n const [language, setLanguage] = useState(null);\n const [dialogOpen, setDialogOpen] = useState(false);\n\n useEffect(() => {\n const fetch = async () => {\n const smsSuggestionLanguage = await getSmsSuggestionLanguageAPI();\n\n if (!Object.keys(smsSuggestionLanguageObj).includes(smsSuggestionLanguage)) {\n throw new Error('Failed to get smsSuggestionLanguage');\n }\n\n setLanguage(smsSuggestionLanguage);\n };\n\n fetch();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const handleChange = (e: SelectChangeEvent) => {\n setLanguage(e.target.value as SmsSuggestionLanguage);\n };\n\n const submit = async () => {\n if (!language) return;\n\n try {\n const response = await setSmsSuggestionLanguageAPI(language);\n handleResponse(response);\n } catch (error) {\n console.error(error);\n }\n };\n\n return (\n \n \n \n \n \n \n \n \n \n \n {Object.keys(smsSuggestionLanguageObj).map((lang: any) => (\n \n \n \n ))}\n \n \n setDialogOpen(true)} sx={{ mt: '10px', minWidth: '150px' }}>\n \n \n \n\n submit()}\n language={language || 'Swedish'}\n />\n \n );\n};\n","import { Translate } from 'react-redux-i18n';\n\nimport { Box, Typography } from '@mui/material';\n\nimport { FlexBox } from 'generalComponents/BoxModifications';\n\nexport const IntelliSmsHeader = (): JSX.Element => {\n return (\n \n \n \n \n \n \n theme.palette.payAttGray.main }}>\n \n \n \n \n \n );\n};\n","import { Helmet } from 'react-helmet-async';\nimport { I18n } from 'react-redux-i18n';\n\nimport { Card, Container } from '@mui/material';\n\nimport { FullDivider } from 'generalComponents/BoxModifications';\n\nimport { ChangeLanguage } from './IntelliSms.ChangeLanguage';\nimport { IntelliSmsHeader } from './IntelliSms.Header';\n\nconst IntelliSmsSettings = (): JSX.Element => {\n return (\n <>\n \n {`${I18n.t('management.settings.intelliSms.title')}`} \n \n \n \n \n \n \n \n \n >\n );\n};\n\nexport default IntelliSmsSettings;\n","import { lazy } from 'react';\nimport { Navigate } from 'react-router-dom';\n\nimport { Loader } from 'routes/routes.helpers';\nimport IntelliSmsSettings from 'views/Management/settings/IntelliSms/IntelliSms';\nimport SidebarLayout from 'views/Navigation/Navigation';\n\nconst Settings = Loader(lazy(() => import('views/Management/settings/VenueSettings/VenueSettings')));\n\nconst Profile = Loader(lazy(() => import('views/Profile/Profile')));\n\nconst IntegrationSettings = Loader(lazy(() => import('views/Management/settings/Integration/IntegrationSettings')));\n\nconst MemberManagement = Loader(lazy(() => import('views/Management/MemberManagement/MemberManagement')));\n\nconst Introduction = Loader(lazy(() => import('views/Management/settings/Introduction/Introduction')));\n\nexport const managementRoutes = {\n path: '/management/*',\n component: ,\n children: [\n { path: '', component: },\n { path: 'memberships', component: },\n {\n path: 'settings/*',\n children: [\n { path: '', component: },\n { path: 'merchant', component: },\n { path: 'intelliSms', component: },\n { path: 'introductions', component: },\n { path: 'integration', component: }\n ]\n },\n { path: 'profile', component: },\n { path: '*', component: }\n ]\n};\n","import { lazy } from 'react';\n\nimport { Loader } from 'routes/routes.helpers';\nimport { SMSCampaignProvider } from 'views/IntelliSms/context/SMSCampaignContext';\nimport SidebarLayout from 'views/Navigation/Navigation';\n\nconst NewCampaign = Loader(lazy(() => import('views/IntelliSms/newCampaign/NewCampaign')));\nconst CampaignsOverview = Loader(lazy(() => import('views/IntelliSms/overview/Overview')));\n\nexport const intelliSmsRoutes = {\n path: '/IntelliSMS/*',\n component: ,\n children: [\n {\n path: 'new',\n component: (\n \n \n \n )\n },\n {\n path: 'overview',\n component: (\n \n \n \n )\n }\n ]\n};\n","import { lazy } from 'react';\nimport { Navigate } from 'react-router';\n\nimport { Loader } from 'routes/routes.helpers';\nimport SidebarLayout from 'views/Navigation/Navigation';\nimport { StampCardMenuProvider } from 'views/StampCardForm/StampCardMenuContext';\n\nconst CreateStampCard = Loader(lazy(() => import('views/StampCardForm/CreateStampCard')));\nconst CampaignsOverview = Loader(lazy(() => import('views/StampCardForm/CampaignsOverview/CampaignsOverview')));\nconst StampCardTriggers = Loader(lazy(() => import('views/StampCardForm/StampCardTriggers/StampCardTriggers')));\n\nexport const stampCardRoutes = {\n path: '/stampcard/*',\n component: (\n \n \n \n ),\n children: [\n {\n path: '',\n component: \n },\n {\n path: 'form',\n component: \n },\n {\n path: 'overview',\n component: \n },\n {\n path: 'trigger',\n component: \n }\n ]\n};\n","import { lazy } from 'react';\nimport { Navigate } from 'react-router-dom';\n\nimport { Loader } from 'routes/routes.helpers';\nimport { SMSCampaignProvider } from 'views/IntelliSms/context/SMSCampaignContext';\nimport SidebarLayout from 'views/Navigation/Navigation';\n\nconst Dashboard = Loader(lazy(() => import('views/Dashboard/Dashboard')));\n\nexport const dashboardRoutes = {\n path: '/dashboard/*',\n component: (\n \n \n \n ),\n children: [\n {\n path: '',\n component: \n },\n {\n path: '*',\n component: \n }\n ]\n};\n","import { lazy } from 'react';\n\nimport { Loader } from 'routes/routes.helpers';\nimport SidebarLayout from 'views/Navigation/Navigation';\n\nconst RegistrationDisplayViews = Loader(lazy(() => import('views/RegistrationDisplayViews/RegistrationDisplayViews')));\n\nexport const displayViewsRoutes = {\n path: '/views',\n component: ,\n children: [{ path: '', component: }]\n};\n","import { login, showContractGuard } from 'store/features/auth/authSlice';\nimport { AuthTokens, CurrentUserInterface, LoginInfo } from 'store/features/auth/handlers';\nimport { getCampaignHistory } from 'store/features/campaigns/campaignHistorySlice';\nimport { getHomepageDashboards } from 'store/features/dashboard/actions';\nimport { ContractStructure, MerchantInterface } from 'store/features/merchantAndVenues/handlers';\nimport { getMerchant } from 'store/features/merchantAndVenues/merchantSlice';\nimport { getVenueCategories } from 'store/features/venueCategories/venueCategorySlice';\nimport { AppDispatch } from 'store/store.exports';\n\nexport const populateDB = async (dispatch: AppDispatch, merchant: MerchantInterface) => {\n try {\n await dispatch(getVenueCategories(merchant));\n } catch (error) {\n console.error('Error fetching SMS suggestions', error);\n }\n\n try {\n await dispatch(getCampaignHistory());\n } catch (error) {\n console.error('Error fetching campaign history', error);\n }\n};\n\nexport const dispatchGetMerchant = async (dispatch: AppDispatch, user: CurrentUserInterface) => {\n try {\n const payload = await dispatch(getMerchant(user));\n const merchant = payload.payload as unknown as MerchantInterface;\n\n if (merchant.merchantTitle !== undefined) {\n await populateDB(dispatch, merchant);\n } else {\n throw Error('Error fetching merchant');\n }\n } catch (error) {\n console.error('Error logging in', error);\n }\n};\n\nexport const dispatchLogin = async (dispatch: AppDispatch, info: LoginInfo) => {\n try {\n const response = await dispatch(login(info));\n\n if ((response.payload as unknown as AuthTokens)?.contract) {\n return response.payload as { contract: ContractStructure; token: string; chartToken: string };\n }\n\n if ((response.payload as unknown as CurrentUserInterface)?.jwtToken !== undefined) {\n dispatchGetMerchant(dispatch, response.payload as unknown as CurrentUserInterface);\n\n dispatch(showContractGuard(true));\n dispatch(getHomepageDashboards());\n } else {\n throw Error(`Failed to fetch token and user from database: ${JSON.stringify(response.payload)}`);\n }\n } catch (error) {\n console.error('Error logging in', error);\n }\n};\n","import { useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\nimport { Navigate, useNavigate } from 'react-router-dom';\n\nimport { Brush, Start } from '@mui/icons-material';\nimport { AppBar, Button, Paper, Toolbar, Typography } from '@mui/material';\n\nimport { format } from 'date-fns';\nimport { isBefore } from 'date-fns/esm';\n\nimport { CenteredFlexBox, FlexBox } from 'generalComponents/BoxModifications';\nimport { LoginInfo } from 'store/features/auth/handlers';\nimport { ContractStructure } from 'store/features/merchantAndVenues/handlers';\nimport { useAppDispatch } from 'store/store.exports';\nimport { delay } from 'utils/utils';\nimport { dispatchLogin } from 'views/Login/helpers';\n\nconst APP_BAR_HEIGHT = '50px';\n\nconst ContractGuardCSS = {\n appBar: { height: APP_BAR_HEIGHT, backgroundColor: '#1a1a1a', display: 'flex', justifyContent: 'center' },\n container: { height: `calc(100vh - ${APP_BAR_HEIGHT})` },\n paper: { p: { xs: 2, sm: 4, md: 6, lg: 8, xl: 10 } },\n signBeforeBox: { justifyContent: 'center' },\n header: {\n textAlign: 'center',\n fontSize: { xl: '1.85rem', lg: '1.7rem', md: '1.5rem', sm: '1.3rem', xs: '1.05rem' },\n mb: 1\n },\n subHeader: {\n textAlign: 'center',\n fontSize: { xl: '1.7rem', lg: '1.55rem', md: '1.35rem', sm: '1.15rem', xs: '0.9rem' },\n mb: 3\n },\n buttonsBox: { justifyContent: 'center', gap: 2, mb: 2 },\n buttons: { fontSize: { xs: 10, sm: 14 } }\n};\n\nexport const ContractBlocker: React.FC<{\n contract: ContractStructure;\n token: string;\n chartToken: string;\n userLoginData: LoginInfo;\n}> = ({ contract, token, chartToken, userLoginData }) => {\n const navigate = useNavigate();\n const dispatch = useAppDispatch();\n\n const signingDateHasNotPassed = isBefore(new Date(), new Date(contract.hasToBeSignedBefore));\n\n const signDateFormatted = format(new Date(contract.hasToBeSignedBefore), 'yyyy-MM-dd hh:mm');\n\n const [goToLogin, setGoToLogin] = useState(false);\n\n return (\n <>\n {goToLogin && }\n \n \n \n \n \n \n PayAtt\n \n \n \n\n \n \n \n \n \n \n \n \n \n {\n setGoToLogin(true);\n }}\n href={contract.userContractSignUrl}\n variant=\"contained\"\n color=\"green\"\n target=\"_blank\"\n sx={{\n ...ContractGuardCSS.buttons,\n '&:hover': { color: 'white' }\n }}\n endIcon={ }>\n \n \n {signingDateHasNotPassed && (\n {\n await dispatchLogin(dispatch, {\n ...userLoginData,\n skipContractCheck: true,\n token,\n chartToken\n });\n\n await delay(200);\n navigate('/home');\n }}\n variant=\"contained\"\n color=\"payAttBlack\"\n sx={ContractGuardCSS.buttons}\n endIcon={ }>\n \n \n )}\n \n {signingDateHasNotPassed && (\n \n \n {signDateFormatted}\n \n \n )}\n \n \n >\n );\n};\n","import { useState } from 'react';\nimport { Helmet } from 'react-helmet-async';\nimport { useForm } from 'react-hook-form';\nimport { useSelector } from 'react-redux';\nimport { I18n, Translate } from 'react-redux-i18n';\nimport { useNavigate } from 'react-router';\n\nimport { Visibility, VisibilityOff } from '@mui/icons-material';\nimport { Box, Button, Card, InputAdornment, TextField, Typography, useTheme } from '@mui/material';\n\nimport { isPayAttError } from 'errorHandling/payattError';\nimport { ContractBlocker } from 'generalComponents/ContractBlocker/ContractBlocker';\nimport LoginLogo from 'generalComponents/PayAttLogo';\nimport { setAuthFailed } from 'store/features/auth/authSlice';\nimport { LoginInfo } from 'store/features/auth/handlers';\nimport { ContractStructure } from 'store/features/merchantAndVenues/handlers';\nimport { authSelector } from 'store/selectors';\nimport { useAppDispatch } from 'store/store.exports';\n\nimport { dispatchLogin } from './helpers';\n\nconst LoginForm = () => {\n const dispatch = useAppDispatch();\n const navigate = useNavigate();\n const theme = useTheme();\n\n const {\n register,\n handleSubmit,\n formState: { errors }\n } = useForm();\n\n const { isLoading, error } = useSelector(authSelector);\n\n const [passwordShown, setPasswordShown] = useState(false);\n\n const togglePasswordShown = () => {\n setPasswordShown((s) => !s);\n };\n\n const [contractAndLoginData, setContractAndLoginData] = useState<{\n contract: ContractStructure;\n token: string;\n chartToken: string;\n userLoginData: LoginInfo;\n } | null>(null);\n\n // eslint-disable-next-line\n const onSubmit = async (data: any): Promise => {\n const info: LoginInfo = {\n username: data.username,\n password: data.password\n };\n if (data.username.includes('@')) {\n info.email = data.username;\n delete info.username;\n }\n\n try {\n const hasContractData = await dispatchLogin(dispatch, info);\n if (hasContractData) setContractAndLoginData({ ...hasContractData, userLoginData: info });\n else navigate('/home');\n } catch (err) {\n if (!isPayAttError(err)) {\n dispatch(\n setAuthFailed({\n authError: true,\n message: I18n.t('general.errors.unknownError')\n })\n );\n } else {\n dispatch(setAuthFailed({ authError: true, message: err.message }));\n }\n }\n };\n\n // We check that contract is not signed in authLogin function\n if (contractAndLoginData) {\n window.history.replaceState(null, '', '/contract');\n\n return (\n <>\n \n Sign contract \n \n ;\n >\n );\n }\n\n return (\n <>\n \n Login \n \n \n \n \n \n >\n );\n};\n\nexport default LoginForm;\n","import { useEffect } from 'react';\nimport { useNavigate } from 'react-router';\n\nimport { authSelector } from 'store/selectors';\nimport { store } from 'store/store';\n\nimport { useHeaderBannersContext } from '../Navigation/Header/Context/useContextProvider';\nimport Login from './LoginForm';\n\n// Component automatically redirects to Home if the user is logged in\nconst LoginView = () => {\n const { removeAllBanners } = useHeaderBannersContext();\n const navigate = useNavigate();\n\n useEffect(() => {\n removeAllBanners();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n if (authSelector(store.getState())?.isAuth) navigate('/home');\n }, [navigate]);\n\n return ;\n};\n\nexport default LoginView;\n","import { useState } from 'react';\nimport { Helmet } from 'react-helmet-async';\nimport { useForm } from 'react-hook-form';\nimport { I18n, Translate } from 'react-redux-i18n';\nimport { useNavigate } from 'react-router-dom';\n\nimport LockResetIcon from '@mui/icons-material/LockReset';\nimport { Button, Card, CardContent, CardHeader, TextField, Typography, useTheme } from '@mui/material';\n\nimport { getResetEmail, verifyToken } from 'api/passwordReset';\nimport { isPayAttError } from 'errorHandling/payattError';\nimport { CenteredFlexBox, VerticalCenteredFlexBox } from 'generalComponents/BoxModifications';\n\nconst ResetPassword = () => {\n const navigate = useNavigate();\n const theme = useTheme();\n\n const initState = { email: '', code: '' };\n const [responseError, setResponseError] = useState({\n status: -1,\n message: ''\n });\n const [codeSent, setCodeSent] = useState(false);\n\n const {\n register,\n handleSubmit,\n getValues,\n formState: { errors },\n watch\n } = useForm({\n mode: 'onBlur',\n defaultValues: initState\n });\n\n const onSubmit = async () => {\n const email = getValues('email');\n const token = getValues('code');\n\n if (codeSent) {\n try {\n const response = await verifyToken(token);\n\n if (response === true) {\n navigate(`/newPassword?token=${token}&email=${email}`);\n } else {\n setResponseError({\n status: 500,\n message: I18n.t('passwordReset.error.unexpectedErrorTokenVerify')\n });\n }\n } catch (error) {\n if (isPayAttError(error) && error.status === 410) {\n setResponseError({\n status: 410,\n message: I18n.t('passwordReset.error.expiredToken')\n });\n }\n setResponseError({\n status: 500,\n message: I18n.t('passwordReset.error.verifyCodeFail')\n });\n }\n } else {\n try {\n const res = await getResetEmail(email);\n\n if (res.status && res.status >= 200 && res.status < 300) {\n setCodeSent(true);\n } else if (res.status) {\n setResponseError({\n status: res.status,\n message: I18n.t('passwordReset.error.failed')\n });\n }\n } catch (err) {\n if (isPayAttError(err)) {\n setResponseError({\n status: err.status,\n message: I18n.t('passwordReset.error.failed')\n });\n } else {\n setResponseError({\n status: 500,\n message: I18n.t('passwordReset.error.failed')\n });\n }\n }\n }\n };\n\n return (\n <>\n \n {I18n.t('passwordReset.title')} \n \n \n \n \n >\n );\n};\n\nexport default ResetPassword;\n","import { useEffect, useMemo, useState } from 'react';\nimport { Helmet } from 'react-helmet-async';\nimport { useForm } from 'react-hook-form';\nimport { I18n, Translate } from 'react-redux-i18n';\nimport { useLocation, useNavigate } from 'react-router-dom';\n\nimport LockResetIcon from '@mui/icons-material/LockReset';\nimport Visibility from '@mui/icons-material/Visibility';\nimport VisibilityOff from '@mui/icons-material/VisibilityOff';\nimport { Button, Card, CardContent, CardHeader, IconButton, InputAdornment, TextField } from '@mui/material';\n\nimport { resetPassword, verifyToken } from 'api/passwordReset';\nimport { isPayAttError } from 'errorHandling/payattError';\nimport { CenteredFlexBox, VerticalFlexBox } from 'generalComponents/BoxModifications';\nimport { PayAttSuccessToastForever } from 'generalComponents/Toasts/Toasts';\nimport { login } from 'store/features/auth/authSlice';\nimport { CurrentUserInterface, LoginInfo } from 'store/features/auth/handlers';\nimport { useAppDispatch } from 'store/store.exports';\nimport { dispatchGetMerchant } from 'views/Login/helpers';\n\nconst URL_PARAM_TOKEN = 'token';\nconst URL_PARAM_EMAIL = 'email';\n\nconst logUserInWithNewPassword = async (info: LoginInfo, dispatch: any, navigate: any) => {\n try {\n const response = await dispatch(login(info));\n\n if ((response.payload as unknown as CurrentUserInterface).jwtToken !== undefined) {\n await dispatchGetMerchant(dispatch, response.payload as unknown as CurrentUserInterface);\n\n navigate('/home');\n } else {\n throw Error(`Failed to fetch token and user from database: ${JSON.stringify(response.payload)}`);\n }\n } catch (error) {\n // This happens if the contract is not signed\n console.error('Error logging in', error);\n navigate('/login');\n }\n};\n\nexport const NewPassword = () => {\n const dispatch = useAppDispatch();\n const navigate = useNavigate();\n const { search } = useLocation();\n const searchParams = useMemo(() => new URLSearchParams(search), [search]);\n const token = searchParams.get(URL_PARAM_TOKEN);\n const email = searchParams.get(URL_PARAM_EMAIL);\n const [serverResponse, setServerResponse] = useState({\n status: -1,\n message: ''\n });\n const [showPassword, setShowPassword] = useState(false);\n const [tokenValid, setTokenValid] = useState(true);\n const [pageLoad, setPageLoad] = useState(true);\n\n const initState = {\n password: '',\n passwordRepeat: ''\n };\n const {\n register,\n handleSubmit,\n getValues,\n formState: { errors, isDirty }\n } = useForm({\n mode: 'all',\n defaultValues: initState\n });\n\n const onSubmit = async () => {\n if (token && email) {\n try {\n const res = await resetPassword(token, email, getValues('password'));\n\n if (res.status >= 200 && res.status < 300) {\n PayAttSuccessToastForever(\n \n \n
\n );\n const info: LoginInfo = { email, password: getValues('password') };\n logUserInWithNewPassword(info, dispatch, navigate);\n }\n\n setServerResponse({\n status: res.status,\n message: res.message\n });\n } catch (error) {\n console.error('Failed to set new password', error);\n\n if (isPayAttError(error)) {\n if (error.message.includes('Invalid token')) {\n setServerResponse({\n status: error.status,\n message: I18n.t('passwordReset.newPassword.error.invalidToken')\n });\n }\n setServerResponse({\n status: error.status,\n message: I18n.t('passwordReset.newPassword.error.serverError')\n });\n }\n }\n } else {\n console.error('Missing token and/or email');\n }\n };\n\n const handleClickShowPassword = () => {\n setShowPassword(!showPassword);\n };\n\n const handleMouseDownPassword = (event: React.MouseEvent) => {\n event.preventDefault();\n };\n\n const validatePassword = (value: any): string | true => {\n if (value.length < 8) {\n return I18n.t('passwordReset.newPassword.error.minPasswordLength');\n }\n\n const regex =\n /^(?=[^A-Z\\n]*[A-Z])(?=[^a-z\\n]*[a-z])(?=[^0-9\\n]*[0-9])(?=[^!@£$%^&*()_+}|\":?><,./\\-=[\\]|~`\"'\\\\\\n]*[!@£$%^&*()_+}|\":?><,./\\-=[\\]|~`\"'\\\\]).{8,}$/;\n\n if (!regex.test(value)) {\n return `${I18n.t('passwordReset.newPassword.error.formatRequirements')}`;\n }\n\n return true;\n };\n\n useEffect(() => {\n const verifyTokenFunc = async () => {\n if (pageLoad && token && email) {\n setPageLoad(false);\n\n try {\n const response = await verifyToken(token);\n\n if (!response) {\n setServerResponse({\n status: 999,\n message: I18n.t('passwordReset.error.unexpectedErrorTokenVerify')\n });\n setTokenValid(false);\n return;\n }\n\n setTokenValid(true);\n } catch (error) {\n setServerResponse({\n status: 999,\n message: I18n.t('passwordReset.error.expiredToken')\n });\n setTokenValid(false);\n }\n }\n };\n\n verifyTokenFunc();\n }, [email, pageLoad, token]);\n\n // if (!token || !email) {\n // return ;\n // }\n\n return (\n <>\n \n {I18n.t('passwordReset.newPassword.title')} \n \n \n \n \n >\n );\n};\n","import { Component } from 'react';\nimport { Navigate, Outlet } from 'react-router-dom';\n\nimport { store } from 'store/store';\n\nconst AuthRoute = (props: any) => {\n const state = store.getState();\n\n if (!state.auth.isAuth) {\n return ;\n }\n\n return } />;\n};\n\nexport default AuthRoute;\n","import { lazy } from 'react';\nimport { Navigate, Route, BrowserRouter as Router, Routes } from 'react-router-dom';\n\nimport {\n builderRoutes,\n contactRoutes,\n dashboardRoutes,\n homeRoutes,\n intelliSmsRoutes,\n managementRoutes,\n stampCardRoutes\n} from 'routes/routeDefinitions';\nimport { displayViewsRoutes } from 'routes/routeDefinitions/displayViews.routes';\nimport { logOut } from 'store/features/auth/actions';\nimport { authSelector } from 'store/selectors';\nimport { store } from 'store/store';\nimport { useAppDispatch } from 'store/store.exports';\nimport LoginView from 'views/Login/Login';\nimport SidebarLayout from 'views/Navigation/Navigation';\nimport ResetPassword from 'views/ResetPassword/ResetPassword';\nimport { NewPassword } from 'views/ResetPassword/newPassword/NewPassword';\n\nimport AuthRoute from './AuthRoute';\nimport { Loader } from './routes.helpers';\n\nconst FAQ = Loader(lazy(() => import('views/Management/FAQ/FaqComponent')));\n\nconst DefaultRoute = () => {\n const state = store.getState();\n\n return state.auth.isAuth ? : ;\n};\n\nconst LogoutRoute = () => {\n const dispatch = useAppDispatch();\n\n // Log us out unless we are already at the login screen\n const auth = authSelector(store.getState());\n if (auth.isAuth) dispatch(logOut());\n\n if (window.location.href.includes('/login')) return null;\n\n return ;\n};\n\n// Get all the routes from a JSON object, including nested routes\n// and Navigator items\nexport const getRoutes = (myRoutes: any) => {\n if (myRoutes.children) {\n return (\n \n {/* Create sub-routes */}\n {myRoutes.children.map((child: any) => getRoutes(child))}\n \n );\n }\n if (myRoutes.component instanceof Navigate) {\n return myRoutes.component;\n }\n return ;\n};\n\n// Declares all Routes for the web app, different base routes are declared in different JSON files,\n// extract values with getRoutes()\nconst MainRoutes = (): JSX.Element => {\n // Add logout handler, to make sure ALL open tabs/windows are logged out\n window.addEventListener('storage', (e) => {\n if (e.newValue && e.oldValue) {\n const newState = JSON.parse(e.newValue);\n const oldState = JSON.parse(e.oldValue);\n\n // Trigger logout in tab\n if (oldState && oldState.auth && oldState.auth.isAuth && !newState?.auth?.isAuth) window.location.reload();\n }\n });\n\n return (\n \n \n } />\n {/* Use below syntax to enfore autheticated routes on ALL private routes */}\n }>\n {getRoutes(homeRoutes)}\n \n }>\n {getRoutes(dashboardRoutes)}\n \n }>\n {getRoutes(intelliSmsRoutes)}\n \n }>\n {getRoutes(stampCardRoutes)}\n \n }>\n {getRoutes(displayViewsRoutes)}\n \n }>\n {getRoutes(managementRoutes)}\n \n }>\n {getRoutes(contactRoutes)}\n \n }>\n {getRoutes(builderRoutes)}\n \n }>\n }>\n } />\n \n \n } />\n } />\n } />\n } />\n \n \n );\n};\n\nexport default MainRoutes;\n","import { Translate } from 'react-redux-i18n';\nimport { toast } from 'react-toastify';\n\nimport { endUserSession } from 'api/auth/authApi';\nimport { PayAttLogOutToast } from 'generalComponents/Toasts/Toasts';\nimport { setAuthFailed, setLoading, setLogOut } from 'store/features/auth/authSlice';\nimport { saveLogoutState } from 'store/localStorageState';\nimport { store } from 'store/store';\nimport { AppThunk } from 'store/store.exports';\nimport { CONTRACT_LOGOUT_TOAST_ID } from 'utils/global.constants';\n\nexport const logOut = (() => {\n /*\n * We are only allowing toasts to be shown once to prevent problems caused by running multiple toasts\n * (ex. something goes wrong when trying to refresh token and we get spammed with toasts)\n */\n let logOutToastWasShown = false;\n let contractToastWasShown = false;\n\n return (args?: { showToast: boolean; contractToast?: boolean }): AppThunk =>\n async (dispatch) => {\n try {\n const onClose = async () => {\n dispatch(setLoading(true));\n await endUserSession();\n saveLogoutState(store.getState());\n dispatch(setLogOut());\n window.location.reload();\n };\n\n const showToast = args?.showToast;\n const contractToast = args?.contractToast;\n const fiveMinutesInMilliseconds = 5 * 60 * 1000;\n const tenSecondsInMilliseconds = 10 * 1000;\n\n if (showToast) {\n const toastDuration = contractToast ? fiveMinutesInMilliseconds : tenSecondsInMilliseconds;\n\n if (contractToast && !contractToastWasShown) {\n const { merchant } = store.getState();\n\n const logoutContractMsg = (\n \n );\n\n toast.warning(logoutContractMsg, {\n toastId: CONTRACT_LOGOUT_TOAST_ID,\n position: 'bottom-center',\n style: {\n width: '800px',\n left: '-50%'\n },\n autoClose: toastDuration,\n closeOnClick: false,\n closeButton: false,\n draggable: false,\n pauseOnHover: false,\n pauseOnFocusLoss: false,\n onClose\n });\n\n contractToastWasShown = true;\n }\n\n if (!contractToast && !logOutToastWasShown) {\n const logOutMessage = ;\n\n PayAttLogOutToast({ onClose, content: logOutMessage, type: 'warn' });\n\n logOutToastWasShown = true;\n }\n } else {\n await onClose();\n }\n } catch (error: any) {\n dispatch(setAuthFailed({ authError: true, message: error.message }));\n } finally {\n dispatch(setLoading(false));\n }\n };\n})();\n","import { useEffect, useState } from 'react';\n\nimport { localeSelector } from 'store/selectors';\nimport { store } from 'store/store';\n\nconst LocaleAPI = () => {\n const [locale, setCurrentLanguage] = useState(localeSelector(store.getState()));\n\n useEffect(() => {\n const unsubscribe = store.subscribe(() => {\n setCurrentLanguage(localeSelector(store.getState()));\n });\n\n return unsubscribe;\n }, [locale]);\n\n return locale;\n};\n\nexport default LocaleAPI;\n","import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';\n\nimport { Action } from '@reduxjs/toolkit';\nimport { ThunkAction } from 'redux-thunk';\n\nimport { store } from 'store/store';\n\nexport type AppDispatch = typeof store.dispatch;\nexport const useAppDispatch = () => useDispatch();\nexport type RootState = ReturnType;\nexport const useAppSelector: TypedUseSelectorHook = useSelector;\nexport type AppThunk = ThunkAction;\n","import { getBillingSettings } from 'store/features/PayAttSettings/billingSettingsSlice';\nimport { updateUser } from 'store/features/auth/authSlice';\nimport { CurrentUserInterface } from 'store/features/auth/handlers';\nimport { getCampaignHistory } from 'store/features/campaigns/campaignHistorySlice';\nimport { MerchantInterface } from 'store/features/merchantAndVenues/handlers';\nimport { getMerchant } from 'store/features/merchantAndVenues/merchantSlice';\nimport { getRegistrationsPerVenue } from 'store/features/registrations/registrationSlice';\nimport { getStampCardCampaigns } from 'store/features/stampCardCampaigns/stampCardFormSlice';\nimport { getVenueCategories } from 'store/features/venueCategories/venueCategorySlice';\n\nimport { getHomepageDashboards } from './features/dashboard/actions';\nimport { AppDispatch } from './store.exports';\n\nexport const PopulateStatistics = async (dispatch: AppDispatch, merchant: MerchantInterface) => {\n const { venues } = merchant;\n if (venues.length === 0) return;\n\n try {\n await dispatch(getRegistrationsPerVenue(venues));\n } catch (error) {\n console.error('Error fetching registration stats', error);\n }\n};\n\nexport const PopulateGeneralStats = async (dispatch: AppDispatch, merchant: MerchantInterface) => {\n const { venues } = merchant;\n if (venues.length === 0) return;\n\n try {\n await dispatch(getRegistrationsPerVenue(venues));\n } catch (error) {\n console.error('Error fetching registration stats', error);\n }\n};\n\nexport const PopulateMerchantAndVenues = async (dispatch: AppDispatch, user: CurrentUserInterface) => {\n try {\n await dispatch(getMerchant(user));\n } catch (error) {\n console.error('Error fetching Merchant and Venues', error);\n }\n};\n\nexport const PopulateUser = async (dispatch: AppDispatch) => {\n await dispatch(updateUser());\n};\n\nexport const PopulateCampaignHistory = async (dispatch: AppDispatch) => {\n await dispatch(getCampaignHistory());\n};\n\nexport const PopulateSmsSuggestions = async (dispatch: AppDispatch, merchant: MerchantInterface) => {\n await dispatch(getVenueCategories(merchant));\n};\n\nexport const PopulateBillingSettings = async (dispatch: AppDispatch) => {\n await dispatch(getBillingSettings());\n};\n\nexport const PopulateStampCardCampaigns = async (dispatch: AppDispatch, merchant: MerchantInterface) => {\n await dispatch(getStampCardCampaigns(merchant));\n};\n\nexport const PopulateHomepageLayouts = async (dispatch: AppDispatch) => dispatch(getHomepageDashboards());\n","export const NOTIFICATION_CHECK_INTERVAL = 10; // in minutes\nexport const CONTRACT_LOGOUT_TOAST_ID = 'contractLogOut';\n","export type Encoding = 'GSM_7BIT' | 'GSM_7BIT_EXT' | 'UTF16';\n\nexport interface TextSummary {\n encoding: Encoding;\n length: number;\n characterPerMessage: number;\n inCurrentMessage: number;\n remaining: number;\n messages: number;\n}\n\nconst GSM_7BIT_REGEXP =\n /^[@£$¥èéùìòÇ\\nØø\\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\"#¤%&'()*+,\\-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà]*$/;\nconst GSM_7BIT_EXT_REGEXP =\n /^[@£$¥èéùìòÇ\\nØø\\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\"#¤%&'()*+,\\-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà^{}\\\\[~\\]|€]*$/;\nconst GSM_7BIT_EXT_CHAR_REGEXP = /^[\\^{}\\\\[~\\]|€]$/;\n\nconst messageLength: { [key in Encoding]: number } = {\n GSM_7BIT: 160,\n GSM_7BIT_EXT: 160,\n UTF16: 70\n};\n\nconst multiMessageLength: { [key in Encoding]: number } = {\n GSM_7BIT: 153,\n GSM_7BIT_EXT: 153,\n UTF16: 67\n};\n\nconst detectEncoding = (text: string): Encoding => {\n switch (false) {\n case text.match(GSM_7BIT_REGEXP) == null:\n return 'GSM_7BIT';\n case text.match(GSM_7BIT_EXT_REGEXP) == null:\n return 'GSM_7BIT_EXT';\n default:\n return 'UTF16';\n }\n};\n\nconst countGsm7bitExt = (text: string): number => {\n const extChar = [];\n for (let i = 0; i < text.length; i += 1) {\n const char = text[i];\n if ((char.match(GSM_7BIT_EXT_CHAR_REGEXP) || []).length > 0) {\n extChar.push(char);\n }\n }\n\n return extChar.length;\n};\n\nexport const countSMSCharacters = (text: string): TextSummary => {\n const encoding = detectEncoding(text);\n let { length } = text;\n\n if (encoding === 'GSM_7BIT_EXT') {\n length += countGsm7bitExt(text);\n }\n\n let characterPerMessage = messageLength[encoding];\n if (length > characterPerMessage) {\n characterPerMessage = multiMessageLength[encoding];\n }\n\n const messages = Math.ceil(length / characterPerMessage);\n\n let inCurrentMessage = length;\n if (messages > 0) {\n inCurrentMessage = length - characterPerMessage * (messages - 1);\n }\n\n let remaining = characterPerMessage * messages - length;\n if (remaining === 0 && messages === 0) {\n remaining = characterPerMessage;\n }\n\n return {\n encoding,\n length,\n characterPerMessage,\n inCurrentMessage,\n remaining,\n messages\n };\n};\n","import { createContext, useContext, useMemo, useState } from 'react';\n\nimport { DashboardBreakpoint } from 'views/Dashboard/Interfaces/Dashboard.breakpoints';\n\nimport { IGridBreakpointsContext } from '../Contexts.interfaces';\n\nexport const GridBreakpointsContext = createContext(null);\n\nexport const GridBreakpointsContextProvider: React.FC<{\n children: React.ReactNode;\n}> = ({ children }) => {\n const [breakpoint, setBreakpoint] = useState(null);\n\n const value = useMemo(\n () => ({\n breakpoint,\n setBreakpoint\n }),\n [breakpoint]\n );\n\n return {children} ;\n};\n\nexport const useBreakpointsContext = () => {\n const breakpointsContext = useContext(GridBreakpointsContext);\n\n if (!breakpointsContext) {\n throw new Error('No breakpointsContext.Provider found when calling useBreakpointsContext');\n }\n\n return breakpointsContext;\n};\n","import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';\n\nimport { getDashboard } from 'store/features/dashboard/actions';\nimport { DashboardDoc, DashboardDocs } from 'store/features/dashboard/handlers';\nimport { allDashboardsSelector } from 'store/selectors';\nimport { store } from 'store/store';\nimport { useAppDispatch } from 'store/store.exports';\n\ninterface DashboardSelectionContext {\n allDashboards: DashboardDocs;\n dashboard: DashboardDoc | null;\n setDashboard: React.Dispatch>;\n isLoading: boolean;\n}\n\nexport const DashboardSelectionContext = createContext(null);\n\nexport const DashboardSelectionProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {\n const dispatch = useAppDispatch();\n const [allDashboards, setAllDashboards] = useState([]);\n const [dashboard, setDashboard] = useState(null);\n const [isLoading, setIsLoading] = useState(true);\n\n // Fetch all dashboards from the server\n useEffect(() => {\n dispatch(getDashboard());\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n const unsubscribe = store.subscribe(() => {\n const dashboards = allDashboardsSelector(store.getState());\n\n if (dashboards.isLoading) setIsLoading(true);\n else {\n if (dashboards.data.length > 0) {\n setAllDashboards(dashboards.data);\n\n // Set the default dashboard for the first time (initial load)\n if (!dashboard) {\n const defaultDashboard = dashboards.data.find((el) => el.default);\n\n setDashboard(defaultDashboard || dashboards.data[0]);\n } else {\n // Check if we updated the selected dashboard\n const selectedDashboard = dashboards.data.find((el) => el.id === dashboard.id);\n if (selectedDashboard) {\n setDashboard(selectedDashboard);\n } else {\n const defaultDashboard = dashboards.data.find((el) => el.default);\n setDashboard(defaultDashboard || dashboards.data[0]);\n }\n }\n }\n\n setIsLoading(false);\n }\n });\n\n return unsubscribe;\n }, [dashboard]);\n\n const value = useMemo(\n () => ({\n allDashboards,\n dashboard,\n setDashboard,\n isLoading\n }),\n [allDashboards, dashboard, isLoading]\n );\n\n return {children} ;\n};\n\nexport const useDashboardSelectionContext = () => {\n const dashboardSelectionContext = useContext(DashboardSelectionContext);\n\n if (!dashboardSelectionContext) {\n throw new Error('No dashboardSelectionContext.Provider found when calling useDashboardSelectionContext');\n }\n\n return dashboardSelectionContext;\n};\n","import React, { createContext, useContext, useMemo, useState } from 'react';\n\nimport ChartsEmbedSDK from '@mongodb-js/charts-embed-dom';\n\nimport { authSelector, stampCardsSelector, venuesSelector } from 'store/selectors';\nimport { store } from 'store/store';\nimport { AllChartFilters } from 'views/Dashboard/Widgets/MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.filters';\n\nimport { GlobalSettingsContextInterface, GlobalSettingsProviderProps } from '../Contexts.interfaces';\n\nconst DEFAULT_PAST_MONTHS_TO_SHOW = 1;\n\nexport const GlobalSettingsContext = createContext(null);\n\nexport const GlobalSettingsContextProvider: React.FC = ({\n children,\n globalGridSettings\n}) => {\n const pastDate = new Date();\n pastDate.setMonth(pastDate.getMonth() - DEFAULT_PAST_MONTHS_TO_SHOW);\n pastDate.setHours(0, 0, 0, 0);\n\n const state = store.getState();\n\n const [globalFilter, setGlobalFilter] = useState({\n forever: false,\n createdAt: { $gte: pastDate, $lte: new Date() },\n venueIdStr: { $in: venuesSelector(state).map((el) => el.id) },\n stampCardCampaignIdStr: { $in: stampCardsSelector({ state }).map((el) => el.id) },\n\n // Not in MongoDB documents, but added to store in DB\n trailingTimeFrame: { unit: 'months', value: DEFAULT_PAST_MONTHS_TO_SHOW },\n hourlyPeriodic: false,\n dailyPeriodic: false\n });\n\n const [globalDragability, setGlobalDragability] = useState(globalGridSettings.globalDragability);\n const [globalResizability, setGlobalResizability] = useState(globalGridSettings.globalResizability);\n const [globalCloseability, setGlobalCloseability] = useState(globalGridSettings.globalCloseability);\n\n const [dashboardSettings] = useState({\n hideAddButton: globalGridSettings.hideAddButton || false,\n hideRemoveButton: globalGridSettings.hideRemoveButton || false\n });\n\n const chartSDK = useMemo(() => {\n return new ChartsEmbedSDK({\n baseUrl: process.env.REACT_APP_PAYATT_CHART_BASE_URL,\n getUserToken: async () => {\n const { currentUser } = authSelector(store.getState());\n if (!currentUser?.chartToken) {\n console.warn('No chart token was found, chartSDK will not work');\n }\n\n return currentUser?.chartToken || 'none';\n }\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const value = useMemo(\n () => ({\n globalFilter,\n setGlobalFilter,\n globalDragability,\n setGlobalDragability,\n globalResizability,\n setGlobalResizability,\n globalCloseability,\n setGlobalCloseability,\n dashboardSettings,\n chartSDK\n }),\n [globalFilter, globalDragability, globalResizability, globalCloseability, dashboardSettings, chartSDK]\n );\n\n return {children} ;\n};\n\nexport const useGlobalSettingsContext = () => {\n const globalSettingsContext = useContext(GlobalSettingsContext);\n\n if (!globalSettingsContext) {\n throw new Error('No globalSettingsContext.Provider found when calling useGlobalSettingsContext');\n }\n\n return globalSettingsContext;\n};\n","import { AllChartFilters } from 'views/Dashboard/Widgets/MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.filters';\n\nexport const cloneChartFilter = (filter: AllChartFilters): AllChartFilters => ({\n forever: filter.forever,\n createdAt: { $gte: new Date(filter.createdAt.$gte), $lte: new Date(filter.createdAt.$lte) },\n trailingTimeFrame: filter.trailingTimeFrame\n ? { unit: filter.trailingTimeFrame.unit, value: filter.trailingTimeFrame.value }\n : undefined,\n dailyPeriodic: filter.dailyPeriodic,\n hourlyPeriodic: filter.hourlyPeriodic,\n stampCardCampaignIdStr: {\n $in: [...filter.stampCardCampaignIdStr.$in]\n },\n venueIdStr: {\n $in: [...filter.venueIdStr.$in]\n }\n});\n\nexport const isFilterFieldEqual = (a: any, b: any): boolean => {\n if (typeof a !== typeof b) return false;\n\n if (a instanceof Date) return b instanceof Date && a.getTime() === b.getTime();\n if (b instanceof Date) return false;\n\n if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {\n return a.every((val, i) => isFilterFieldEqual(val, b[i]));\n }\n if (Array.isArray(a) || Array.isArray(b)) return false;\n\n if (['bigInt', 'boolean', 'number', 'string', 'undefined', 'function', 'symbol'].includes(typeof a)) return a === b;\n\n if (\n typeof a === 'object' &&\n typeof b === 'object' &&\n JSON.stringify(Object.keys(a).sort()) === JSON.stringify(Object.keys(b).sort())\n ) {\n return Object.keys(a)\n .map((val) => isFilterFieldEqual(a[val], b[val]))\n .every((el) => el === true);\n }\n\n return false;\n};\n\nexport const isFilterEqual = (a: AllChartFilters, b: AllChartFilters): boolean => {\n if (\n typeof a !== 'object' ||\n typeof b !== 'object' ||\n JSON.stringify(Object.keys(a).sort()) !== JSON.stringify(Object.keys(b).sort())\n ) {\n return false;\n }\n\n return Object.keys(a)\n .map((val) => isFilterFieldEqual(a[val as keyof AllChartFilters], b[val as keyof AllChartFilters]))\n .every((el) => el === true);\n};\n","import { useCallback, useState } from 'react';\nimport ReactGridLayout from 'react-grid-layout';\nimport uuid from 'react-uuid';\n\nimport { MAX_NUMBER_OF_WIDGETS_PER_DASHBOARD } from 'api/dashboard/interface';\nimport { DashboardBreakpoint } from 'views/Dashboard/Interfaces/Dashboard.breakpoints';\nimport { RESPONSIVE_COLUMNS } from 'views/Dashboard/Interfaces/Dashboard.constants';\nimport { DashboardItemLayout, DashboardLayout } from 'views/Dashboard/Interfaces/Dashboard.layout';\nimport { ChartDefaultLayouts } from 'views/Dashboard/Widgets/MongoDBWidgets/ChartSpecification/ChartSpecification.DefaultLayouts';\nimport { ChartNames } from 'views/Dashboard/Widgets/MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.names';\nimport { PayAttWidgetDefaultLayouts } from 'views/Dashboard/Widgets/PayAttWidgets/PayAttWidgets.DefaultLayouts';\nimport { PayAttWidgetNames } from 'views/Dashboard/Widgets/PayAttWidgets/PayAttWidgets.names';\n\nconst ASSUMED_MAX_NUMBER_OF_ROWS = 2000;\n\nexport const getUniqueItemID = (gridItems: DashboardLayout, newItemI: string, id: string): string => {\n const val = `${newItemI}:::${id}`;\n\n if (gridItems.find((el) => el.i === val)) {\n return getUniqueItemID(gridItems, newItemI, uuid());\n }\n\n return val;\n};\n\n// This could cause the widgets to expand larger than they previously were, which could render the dashboard layout\n// ugly, with elements shifting to new rows and changing heights. USE WITH CAUTION.\n/* eslint-disable no-param-reassign */\nexport const validateAndAdjustLayout = (item: DashboardItemLayout) => {\n // min/max values in code should always take precedence\n const { layoutType } = item;\n\n if (Object.keys(ChartNames).includes(layoutType)) {\n item.minW = ChartDefaultLayouts[layoutType as keyof typeof ChartNames].minW;\n item.minH = ChartDefaultLayouts[layoutType as keyof typeof ChartNames].minH;\n item.maxW = ChartDefaultLayouts[layoutType as keyof typeof ChartNames].maxW;\n item.maxH = ChartDefaultLayouts[layoutType as keyof typeof ChartNames].maxH;\n } else if (Object.keys(PayAttWidgetNames).includes(layoutType)) {\n item.minW = PayAttWidgetDefaultLayouts[layoutType as keyof typeof PayAttWidgetNames].minW;\n item.minH = PayAttWidgetDefaultLayouts[layoutType as keyof typeof PayAttWidgetNames].minH;\n item.maxW = PayAttWidgetDefaultLayouts[layoutType as keyof typeof PayAttWidgetNames].maxW;\n item.maxH = PayAttWidgetDefaultLayouts[layoutType as keyof typeof PayAttWidgetNames].maxH;\n }\n\n // Adjust w and height if needed\n if (item.w < item.minW) item.w = item.minW;\n if (item.w > item.maxW) item.w = item.maxW;\n if (item.h < item.minH) item.h = item.minH;\n if (item.h > item.maxH) item.h = item.maxH;\n};\n/* eslint-enable no-param-reassign */\n\nconst doesFit = ({\n y,\n x,\n width,\n height,\n grid\n}: {\n y: number;\n x: number;\n width: number;\n height: number;\n grid: boolean[][];\n}): boolean => {\n for (let i = y; i < y + height; i += 1) {\n for (let j = x; j < x + width; j += 1) {\n if (grid[i][j]) return false;\n }\n }\n\n return true;\n};\n\nconst markAsOccupied = (item: ReactGridLayout.Layout, grid: boolean[][]) => {\n for (let { y } = item; y < item.y + item.h; y += 1) {\n for (let { x } = item; x < item.x + item.w; x += 1) {\n // eslint-disable-next-line no-param-reassign\n grid[y][x] = true;\n }\n }\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst getItemPosition = ({\n width,\n height,\n layout,\n breakpoint\n}: {\n width: number;\n height: number;\n layout: ReactGridLayout.Layout[];\n breakpoint: DashboardBreakpoint;\n}): { x: number; y: number } => {\n const cols = RESPONSIVE_COLUMNS[breakpoint];\n const rows = ASSUMED_MAX_NUMBER_OF_ROWS;\n\n const grid = Array.from(Array(rows), () => Array(cols).fill(false));\n layout.forEach((item) => markAsOccupied(item, grid));\n\n for (let y = 0; y < rows - height + 1; y += 1) {\n for (let x = 0; x < cols - width + 1; x += 1) {\n if (doesFit({ x, y, width, height, grid })) return { x, y };\n }\n }\n\n return { x: 0, y: ASSUMED_MAX_NUMBER_OF_ROWS };\n};\n\nexport const useGridItems = () => {\n const [gridItems, setGridItems] = useState([]);\n const [layout, setLayout] = useState([]);\n\n const addGridItem = useCallback(\n (item: DashboardItemLayout, breakpoint: DashboardBreakpoint, calculateGridItemPosition = false): boolean => {\n if (gridItems.length >= MAX_NUMBER_OF_WIDGETS_PER_DASHBOARD) return false;\n\n setGridItems((currentGridItems) => {\n const newGridItem = { ...item } as DashboardItemLayout;\n newGridItem.i = getUniqueItemID(currentGridItems, item.layoutType, uuid());\n if (!calculateGridItemPosition) {\n newGridItem.y = ASSUMED_MAX_NUMBER_OF_ROWS;\n newGridItem.x = 0;\n } else {\n const { x, y } = getItemPosition({\n width: newGridItem.w,\n height: newGridItem.h,\n layout,\n breakpoint\n });\n\n newGridItem.x = x;\n newGridItem.y = y;\n }\n\n return [...currentGridItems, newGridItem];\n });\n\n return true;\n },\n [gridItems.length, layout]\n );\n\n const addGridItems = useCallback((items: DashboardLayout) => {\n const itemsToAdd: DashboardItemLayout[] = [];\n\n for (let n = 0; n < items.length; n += 1) {\n const item = items[n];\n const newGridItem = { ...item };\n newGridItem.i = getUniqueItemID(itemsToAdd, item.layoutType, uuid());\n\n itemsToAdd.push(newGridItem);\n }\n\n setGridItems(itemsToAdd);\n }, []);\n\n const removeGridItem = useCallback((i: string) => {\n setGridItems((currentGridItems) => currentGridItems.filter((el) => el.i !== i));\n }, []);\n\n const updateGridItem = useCallback((item: DashboardItemLayout) => {\n setGridItems((currentGridItems) =>\n currentGridItems.map((el) => {\n if (el.i === item.i) return { ...el, ...item };\n\n return { ...el };\n })\n );\n }, []);\n\n return [gridItems, addGridItem, addGridItems, removeGridItem, updateGridItem, layout, setLayout] as const;\n};\n","import React, { createContext, useContext, useMemo } from 'react';\n\nimport { GridItemContextInterface } from '../Contexts.interfaces';\nimport { useGridItems } from './useGridItems';\n\nexport const GridItemContext = createContext(null);\n\nexport const GridItemContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {\n const [gridItems, addGridItem, addGridItems, removeGridItem, updateGridItem, layout, setLayout] = useGridItems();\n\n const value = useMemo(\n () => ({\n gridItems,\n addGridItem,\n addGridItems,\n updateGridItem,\n removeGridItem,\n\n // Responsive Grid Layout\n layout,\n setLayout\n }),\n [gridItems, addGridItem, addGridItems, updateGridItem, removeGridItem, layout, setLayout]\n );\n\n return {children} ;\n};\n\nexport const useGridItemContext = () => {\n const gridItemContext = useContext(GridItemContext);\n\n if (!gridItemContext) {\n throw new Error('No GridItemContextProvider.Provider found when calling useGridItemContext');\n }\n\n return gridItemContext;\n};\n","import { createContext, useContext, useEffect, useMemo, useState } from 'react';\n\nimport { StampCardCampaignStats } from 'api/interfaces';\nimport { getAllStampCardCampaignStatsAPI } from 'api/stampCards/Form/getStampCardCampaignStatsAPI';\n\nimport { IStampCardCampaignStatsContext, StampCardCampaignStatsContextProps } from '../Contexts.interfaces';\n\n// Some components require stamp card campaign statisics (number of stamps, campaigns, etc.)\n// By wrapping the dashboard in this context, we can make sure to ONLY fetch stats if there is at least 1 component that\n// requires it. The stats are then shared across all grid items.\n\nexport const StampCardCampaignStatsContext = createContext(null);\n\nexport const StampCardCampaignStatsContextProvider: React.FC = ({ children }) => {\n const [stats, setStats] = useState([]);\n\n const updateStats = async () => {\n try {\n const data = await getAllStampCardCampaignStatsAPI();\n if (data.length) setStats(data);\n } catch (err) {\n console.error(err);\n }\n };\n\n useEffect(() => {\n updateStats();\n }, []);\n\n const value = useMemo(\n () => ({\n stats,\n updateStats\n }),\n [stats]\n );\n\n return {children} ;\n};\n\nexport const useStampCardCampaignStatsContext = () => {\n const chartSettingsContext = useContext(StampCardCampaignStatsContext);\n\n if (!chartSettingsContext) {\n throw new Error(\n 'No StampCardCampaignStatsContext.Provider found when calling useStampCardCampaignStatsContext'\n );\n }\n\n return chartSettingsContext;\n};\n","import { Responsive } from 'react-grid-layout';\n\nimport { Breakpoint, Theme } from '@mui/material';\n\nexport const dashboardMaxWidth = (theme: Theme) => `calc(${theme.breakpoints.values.xl}px + 400px)`;\n\nexport const dashboardContainerBreakpoint = (\n breakpoints: { [key in Breakpoint]: number },\n containerWidth: number | undefined\n) => {\n return (Responsive as any).utils.getBreakpointFromWidth(breakpoints, containerWidth);\n};\n","import { JoyridePayAtt } from 'Joyride/JoyridePayAtt';\n\nimport { homeWidgetMenuIntroSteps } from './Home.JoyrideWidgetMenu.Steps';\nimport { useHomeWidgetMenuJoyrideContext } from './Home.JoyrideWidgetMenu.context';\n\nexport const HomeWidgetMenuJoyrideIntro = () => {\n const { isRunning, stepIndex, handleJoyrideCallback, isManuallyStartedIntro } = useHomeWidgetMenuJoyrideContext();\n\n if (!isRunning) return null;\n\n return (\n \n );\n};\n","import { AllChartFilters } from '../Widgets/MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.filters';\nimport { ChartNames } from '../Widgets/MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.names';\nimport { PayAttWidgetNames } from '../Widgets/PayAttWidgets/PayAttWidgets.names';\nimport { DashboardBreakpoint } from './Dashboard.breakpoints';\n\nexport const GridItemKeys = { ...ChartNames, ...PayAttWidgetNames } as const;\n\n// DashboardItemLayout must match backend structure\nexport interface DashboardItemLayout extends ReactGridLayout.Layout {\n layoutType: keyof typeof GridItemKeys;\n i: string; // Automatically generated from the type to be unique, when added to the grid\n requiresStampCardStats: boolean;\n filter?: AllChartFilters;\n\n // Make the follow layout props required\n minW: number;\n minH: number;\n maxW: number;\n maxH: number;\n w: number;\n h: number;\n}\n\nexport type DashboardLayout = DashboardItemLayout[];\n\nconst dashboardBreakpointLayouts: { [key in DashboardBreakpoint]: DashboardLayout } = {\n xl: [],\n lg: [],\n md: [],\n sm: [],\n xs: []\n};\n\nexport type DashboardBreakpointLayouts = typeof dashboardBreakpointLayouts;\n","import { useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Box, List, ListItem, ListProps, Select, SelectChangeEvent, Typography } from '@mui/material';\nimport FormControl from '@mui/material/FormControl';\nimport MenuItem from '@mui/material/MenuItem';\n\nimport { JOYRIDE_OPTIONS } from 'Joyride/JoyridePayAtt';\n\nimport { FullDivider } from 'generalComponents/BoxModifications';\nimport { setBackgroundImage } from 'store/features/dashboard/dashboardSlice';\nimport { BackgroundImageType, BackgroundImages } from 'store/features/dashboard/handlers';\nimport { dashboardBackgroundImageSelector } from 'store/selectors';\nimport { store } from 'store/store';\nimport { useAppDispatch } from 'store/store.exports';\n\nexport const SelectBackgroundImage: React.FC<{ ListProps: ListProps }> = ({ ListProps: ListProperties }) => {\n const dispatch = useAppDispatch();\n\n const { sx: ListPropSx, ...ListPropsRest } = ListProperties;\n\n const [image, setImage] = useState(dashboardBackgroundImageSelector(store.getState()).name);\n const [open, setOpen] = useState(false);\n\n const handleClose = () => {\n setOpen(false);\n };\n\n const handleOpen = () => {\n setOpen(true);\n };\n\n const handleChange = (event: SelectChangeEvent) => {\n dispatch(setBackgroundImage({ name: event.target.value as BackgroundImageType }));\n setImage(event.target.value as BackgroundImageType);\n };\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n );\n};\n","import { Translate } from 'react-redux-i18n';\n\nimport { Alert, AlertTitle, Button, Dialog, DialogActions, DialogContent, Typography } from '@mui/material';\n\nimport { SlideUpTransition } from 'generalComponents/Transitions/SlideUp';\nimport { DashboardDoc } from 'store/features/dashboard/handlers';\nimport { useAppDispatch } from 'store/store.exports';\n\nimport { deleteDashboard } from '../../../Grid/ResponsiveGridLayout.saveCreate';\n\nexport const ConfirmDeleteModal: React.FC<{\n dashboard: DashboardDoc;\n deleteDialogOpen: boolean;\n setDeleteDialogOpen: React.Dispatch>;\n setIsLoading: React.Dispatch>;\n}> = ({ dashboard, deleteDialogOpen, setDeleteDialogOpen, setIsLoading }) => {\n const dispatch = useAppDispatch();\n const handleClose = () => {\n setDeleteDialogOpen(false);\n };\n\n const onSubmit = async () => {\n setIsLoading(true);\n const response = await deleteDashboard({ id: dashboard.id, name: dashboard.name, dispatch });\n setIsLoading(false);\n\n // This means the dashboard has been deleted, the store should already have been updated to reflect this in redux\n if (response) setDeleteDialogOpen(false);\n };\n\n return (\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n );\n};\n","import { useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Box, Button, Tooltip } from '@mui/material';\n\nimport { CenteredFlexBox } from 'generalComponents/BoxModifications';\nimport { DashboardDoc } from 'store/features/dashboard/handlers';\nimport { introStepDashboard } from 'views/Dashboard/JoyrideIntroductions/Dashboard.Joyride.Steps';\n\nimport { ConfirmDeleteModal } from './DangerZone.ConfirmDeleteModal';\n\nexport const DeleteDashboard: React.FC<{ dashboard: DashboardDoc }> = ({ dashboard }) => {\n const [isLoading, setIsLoading] = useState(false);\n const [modalIsOpen, setModalIsOpen] = useState(false);\n\n const handleClick = async () => {\n if (!modalIsOpen) setModalIsOpen(true);\n };\n\n const deleteButton = () => (\n \n \n \n );\n\n return (\n \n {dashboard.default ? (\n \n }\n placement=\"top\"\n enterDelay={500}\n arrow>\n {deleteButton()} \n \n ) : (\n deleteButton()\n )}\n\n \n \n );\n};\n","import { I18n, Translate } from 'react-redux-i18n';\n\nimport { Box, ListItem, Tooltip, Typography, useTheme } from '@mui/material';\n\nimport { DashboardDoc } from 'store/features/dashboard/handlers';\nimport { useBreakpointsContext } from 'views/Dashboard/Contexts/BreakpointsContext/BreakpointsContext';\nimport { introStepDashboard } from 'views/Dashboard/JoyrideIntroductions/Dashboard.Joyride.Steps';\n\nexport const DashboardSizeItem: React.FC<{ dashboard: DashboardDoc }> = ({ dashboard }) => {\n const theme = useTheme();\n\n const { breakpoint } = useBreakpointsContext();\n\n return (\n \n \n \n \n \n \n \n \n \n \n }\n placement=\"top\"\n arrow\n enterDelay={500}>\n \n :{' '}\n \n \n \n \n \n \n );\n};\n","import { useState } from 'react';\nimport { I18n } from 'react-redux-i18n';\n\nimport { Edit, EditOff } from '@mui/icons-material';\nimport { IconButton, ListItem, TextField, Tooltip, useTheme } from '@mui/material';\n\nimport { DASHBOARD_VALID_NAME_REG_EXP, DASHBOARD_WHILE_TYPING_REG_EXP } from 'api/dashboard/interface';\nimport { FlexBox } from 'generalComponents/BoxModifications';\nimport { useAppDispatch } from 'store/store.exports';\nimport { introStepDashboard } from 'views/Dashboard/JoyrideIntroductions/Dashboard.Joyride.Steps';\n\nimport { saveDashboardName } from '../../../Grid/ResponsiveGridLayout.saveCreate';\n\nconst HiddenElementToAdjustFlexAlignment = () => {\n return (\n \n \n \n \n \n );\n};\n\nexport const TitleWithEditButton: React.FC<{ dashboardId: string; initValue: string }> = ({\n dashboardId,\n initValue\n}) => {\n const theme = useTheme();\n const dispatch = useAppDispatch();\n\n const [value, setValue] = useState(initValue);\n const [isEditing, setIsEditing] = useState(false);\n\n const updateValue = (val: string) => {\n if (DASHBOARD_WHILE_TYPING_REG_EXP.test(val)) setValue(val);\n };\n\n const saveName = async () => {\n if (value !== initValue) {\n const response = await saveDashboardName({ dashboardId, name: value, dispatch });\n if (!response) return;\n }\n\n setIsEditing(false);\n };\n\n const inputStyle = {\n p: '10px',\n opacity: `1 !important`,\n textAlign: 'center !important',\n fontSize: '220%',\n width: '260px'\n };\n\n const disabledInputProps = {\n disableUnderline: true,\n inputProps: { sx: { ...inputStyle, WebkitTextFillColor: '#223354 !important' } }\n };\n\n const enabledInputProps = { inputProps: { sx: inputStyle } };\n\n return (\n \n \n\n \n {\n e.stopPropagation();\n updateValue(e.target.value);\n }}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => {\n if (e.key === 'Enter') saveName();\n e.stopPropagation();\n }}\n disabled={!isEditing}\n error={!DASHBOARD_VALID_NAME_REG_EXP.test(value)}\n />\n \n\n \n {\n e.preventDefault();\n e.stopPropagation();\n if (isEditing) saveName();\n else setIsEditing(true);\n }}\n sx={{ color: theme.palette.payAttDarkBlue.dark }}>\n {isEditing ? : }\n \n \n \n );\n};\n","import { useState } from 'react';\nimport { I18n, Translate } from 'react-redux-i18n';\nimport { toast } from 'react-toastify';\n\nimport { Checkbox, CircularProgress, List, ListItem, Tooltip } from '@mui/material';\n\nimport { PayAttSuccess } from 'errorHandling/payattSuccess';\nimport { updateDashboardDefault } from 'store/features/dashboard/actions';\nimport { DashboardDoc } from 'store/features/dashboard/handlers';\nimport { useAppDispatch } from 'store/store.exports';\nimport { introStepDashboard } from 'views/Dashboard/JoyrideIntroductions/Dashboard.Joyride.Steps';\n\nimport { DashboardSizeItem } from './DashboardSettings.SizeItem';\nimport { TitleWithEditButton } from './DashboardSettings.TitleItem';\n\nexport const DashboardSettings: React.FC<{ dashboard: DashboardDoc }> = ({ dashboard }) => {\n const dispatch = useAppDispatch();\n\n const [checked, setChecked] = useState(dashboard.default || false);\n const [isLoading, setIsLoading] = useState(false);\n\n const handleClick = async () => {\n if (dashboard.default) {\n toast.warning(I18n.t('dashboard.toasts.cannotDeselectDefault'), { autoClose: 10000 });\n return;\n }\n\n try {\n setIsLoading(true);\n const response = (await dispatch(updateDashboardDefault({ id: dashboard.id }))) as {\n payload: PayAttSuccess;\n };\n\n if (response.payload?.status === 200) setChecked(true);\n else throw response;\n\n setIsLoading(false);\n } catch (error) {\n console.error(error);\n\n toast.error(I18n.t('dashboard.toasts.errorUpdatingDefault', { name: dashboard.name }), {\n autoClose: 10000\n });\n }\n };\n\n return (\n \n \n \n \n \n \n :\n \n \n \n {isLoading && }\n \n
\n );\n};\n","import { ChartCategories } from '../Widgets/MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.names';\n\nexport interface DashboardSettings {\n hideAddButton?: boolean;\n hideRemoveButton?: boolean;\n}\n\nexport enum AccordionLabels {\n GlobalFilters = 'GlobalFilters',\n ComponentMenu = 'ComponentMenu',\n DangerZone = 'DangerZone'\n}\n\nexport type ComponentCategories = ChartCategories | 'Others';\n\nexport const ComponentCategoryEnum: Record = {\n TimeBasedCharts: 'TimeBasedCharts',\n NumericCharts: 'NumericCharts',\n DonutCharts: 'DonutCharts',\n HistogramCharts: 'HistogramCharts',\n Others: 'Others'\n};\n","import { AllPayAttWidgetStructure } from './Interfaces/PayAttWidgets.interfaces.structure';\nimport { PayAttWidgetDefaultLayouts } from './PayAttWidgets.DefaultLayouts';\n\nexport const PayAttWidgetsSpecifications: AllPayAttWidgetStructure = {\n sendSMSCampaign: { defaultLayout: PayAttWidgetDefaultLayouts.sendSMSCampaign },\n numberOfStampsAndStampCardsSentence: {\n defaultLayout: PayAttWidgetDefaultLayouts.numberOfStampsAndStampCardsSentence\n },\n increasedRevenueBasedOnVisits: {\n defaultLayout: PayAttWidgetDefaultLayouts.increasedRevenueBasedOnVisits\n },\n increasedRevenueBasedOnSMSCampaign: {\n defaultLayout: PayAttWidgetDefaultLayouts.increasedRevenueBasedOnSMSCampaign\n },\n increasedRevenueBasedOnStampCardCampaign: {\n defaultLayout: PayAttWidgetDefaultLayouts.increasedRevenueBasedOnStampCardCampaign\n }\n};\n","import { DashboardItemLayout, GridItemKeys } from 'views/Dashboard/Interfaces/Dashboard.layout';\nimport { ChartSpecification } from 'views/Dashboard/Widgets/MongoDBWidgets/ChartSpecification/ChartSpecification';\nimport { PayAttWidgetsSpecifications } from 'views/Dashboard/Widgets/PayAttWidgets/PayAttWidgets.specifications';\n\nexport const LayoutMap: { [key in keyof typeof GridItemKeys]: DashboardItemLayout } = {\n numberOfStampsPerStampCardCampaignHistogram:\n ChartSpecification.numberOfStampsPerStampCardCampaignHistogram.defaultLayout,\n averageStampsPerStampCardDonut: ChartSpecification.averageStampsPerStampCardDonut.defaultLayout,\n SMSCampaignsTotal: ChartSpecification.SMSCampaignsTotal.defaultLayout,\n campaignSMSSentTotal: ChartSpecification.campaignSMSSentTotal.defaultLayout,\n numberOfStampsTotal: ChartSpecification.numberOfStampsTotal.defaultLayout,\n stampCardsRewardsClaimedTotal: ChartSpecification.stampCardsRewardsClaimedTotal.defaultLayout,\n stampCardsCreatedTotal: ChartSpecification.stampCardsCreatedTotal.defaultLayout,\n numberOfRegistrationsTotal: ChartSpecification.numberOfRegistrationsTotal.defaultLayout,\n numberOfUniqueRegistrationsTotal: ChartSpecification.numberOfUniqueRegistrationsTotal.defaultLayout,\n registrationsOverTime: ChartSpecification.registrationsOverTime.defaultLayout,\n registrationsOverTimePerVenue: ChartSpecification.registrationsOverTimePerVenue.defaultLayout,\n registrationsNewOrReturningOverTime: ChartSpecification.registrationsNewOrReturningOverTime.defaultLayout,\n purchasesOverTime: ChartSpecification.purchasesOverTime.defaultLayout,\n newStampCardsOverTime: ChartSpecification.newStampCardsOverTime.defaultLayout,\n numberOfStampsOverTime: ChartSpecification.numberOfStampsOverTime.defaultLayout,\n numberOfStampsOverTimePerVenue: ChartSpecification.numberOfStampsOverTimePerVenue.defaultLayout,\n numberOfStampsNewOrReturningOverTime: ChartSpecification.numberOfStampsNewOrReturningOverTime.defaultLayout,\n sendSMSCampaign: PayAttWidgetsSpecifications.sendSMSCampaign.defaultLayout,\n numberOfStampsAndStampCardsSentence: PayAttWidgetsSpecifications.numberOfStampsAndStampCardsSentence.defaultLayout,\n increasedRevenueBasedOnVisits: PayAttWidgetsSpecifications.increasedRevenueBasedOnVisits.defaultLayout,\n increasedRevenueBasedOnSMSCampaign: PayAttWidgetsSpecifications.increasedRevenueBasedOnSMSCampaign.defaultLayout,\n increasedRevenueBasedOnStampCardCampaign:\n PayAttWidgetsSpecifications.increasedRevenueBasedOnStampCardCampaign.defaultLayout\n};\n","import { SyntheticEvent, useEffect, useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\nimport { toast } from 'react-toastify';\n\nimport { ExpandMore } from '@mui/icons-material';\nimport { Accordion, AccordionDetails, AccordionSummary, Box, Button, List, ListItem, Typography } from '@mui/material';\n\nimport { JOYRIDE_TIMEOUT_ACCORDION_OPEN } from 'Joyride/JoyridePayAtt.constants';\n\nimport { MAX_NUMBER_OF_WIDGETS_PER_DASHBOARD } from 'api/dashboard/interface';\nimport { CenteredFlexBox } from 'generalComponents/BoxModifications';\nimport { merchantSelector, stampCardSubscriptionSelector } from 'store/selectors';\nimport { store } from 'store/store';\nimport { merchantIsIntegrated } from 'utils/utils';\nimport { useBreakpointsContext } from 'views/Dashboard/Contexts/BreakpointsContext/BreakpointsContext';\nimport { useStampCardCampaignStatsContext } from 'views/Dashboard/Contexts/StampCardCampaignStatsContext/StampCardCampaignStatsContext';\nimport { DashboardItemLayout, GridItemKeys } from 'views/Dashboard/Interfaces/Dashboard.layout';\nimport { useDashboardJoyrideContext } from 'views/Dashboard/JoyrideIntroductions/Dashboard.Joyride.context';\nimport {\n ChartsThatRequireIntegration,\n ChartsThatRequireStampCardFeature,\n DonutCharts,\n HistogramCharts,\n NumericCharts,\n TimeBasedCharts\n} from 'views/Dashboard/Widgets/MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.names';\nimport { PayAttWidgetNames } from 'views/Dashboard/Widgets/PayAttWidgets/PayAttWidgets.names';\n\nimport { useGlobalSettingsContext } from '../../../Contexts/GlobalSettingsContext/GlobalSettingContext';\nimport { useGridItemContext } from '../../../Contexts/GridItemContext/GridItemContext';\nimport { ComponentCategories, ComponentCategoryEnum } from '../../interfaces';\nimport { LayoutMap } from './WidgetMenuSection.LayoutMap';\n\nexport const ComponentMenuSection = () => {\n const { isRunning, refreshTooltip } = useDashboardJoyrideContext();\n\n const { dashboardSettings } = useGlobalSettingsContext();\n const { addGridItem } = useGridItemContext();\n\n const { updateStats: updateStampCardStats } = useStampCardCampaignStatsContext();\n const { breakpoint } = useBreakpointsContext();\n\n // Only display Stamp Card related chart items if we have at least one venue subscribed\n const subscribedToStampCardFeature = stampCardSubscriptionSelector(store.getState()).length > 0;\n\n // Only display integrated items if we are integrated\n const integrated = merchantIsIntegrated({ merchant: merchantSelector(store.getState()) });\n\n // Only allow one accordion to be open at a time\n const [accordionExpanded, setAccordionExpanded] = useState(false);\n const handleAccordionClick = (label: ComponentCategories) => (_: SyntheticEvent, isExpanded: boolean) => {\n setAccordionExpanded(isExpanded ? label : false);\n };\n\n useEffect(() => {\n if (isRunning) refreshTooltip(JOYRIDE_TIMEOUT_ACCORDION_OPEN);\n }, [isRunning, accordionExpanded, refreshTooltip]);\n\n const addNewGridItem = (gridItemLayout: DashboardItemLayout) => {\n if (breakpoint) {\n const success = addGridItem(gridItemLayout, breakpoint, true);\n if (!success) {\n toast.error(\n \n );\n } else if (gridItemLayout.requiresStampCardStats) updateStampCardStats();\n return;\n }\n\n console.error(\n 'No breakpoint set for dashboard, make sure the dahsboard sets/updates the BreakpointsContext when loaded and on all resize events that cause a breakpoint change'\n );\n };\n\n if (dashboardSettings.hideAddButton) return null;\n\n const chartItem = ({\n name,\n gridItemLayout\n }: {\n name: keyof typeof GridItemKeys;\n gridItemLayout: DashboardItemLayout;\n }) => {\n if (\n ChartsThatRequireStampCardFeature.includes(gridItemLayout.layoutType as any) &&\n !subscribedToStampCardFeature\n ) {\n return null;\n }\n\n if (ChartsThatRequireIntegration.includes(gridItemLayout.layoutType as any) && !integrated) return null;\n\n return (\n \n \n addNewGridItem(gridItemLayout)}>\n \n \n \n \n \n \n \n \n \n );\n };\n\n const chartGroup = ({ title, group }: { title: ComponentCategories; group: Partial }) => {\n const items = Object.keys(group)\n .map((name) =>\n chartItem({\n name: name as keyof typeof GridItemKeys,\n gridItemLayout: LayoutMap[name as keyof typeof GridItemKeys]\n })\n )\n .filter((el) => el !== null);\n\n if (!items.length) return null;\n\n return (\n \n }>\n \n \n \n \n {items} \n \n );\n };\n\n const gridItems = () => {\n return (\n \n {chartGroup({ title: ComponentCategoryEnum.TimeBasedCharts, group: TimeBasedCharts })}\n {chartGroup({ title: ComponentCategoryEnum.NumericCharts, group: NumericCharts })}\n {chartGroup({ title: ComponentCategoryEnum.DonutCharts, group: DonutCharts })}\n {chartGroup({ title: ComponentCategoryEnum.HistogramCharts, group: HistogramCharts })}\n {chartGroup({ title: ComponentCategoryEnum.Others, group: PayAttWidgetNames })}\n \n );\n };\n\n return {gridItems()}
;\n};\n","import React, { useEffect, useRef, useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Close, ExpandMore } from '@mui/icons-material';\nimport SettingsIcon from '@mui/icons-material/Settings';\nimport {\n Accordion,\n AccordionDetails,\n AccordionSummary,\n Box,\n Button,\n Container,\n Popover,\n Typography,\n useTheme\n} from '@mui/material';\n\nimport { JOYRIDE_TIMEOUT_MENU_OPEN, SHOW_HOMEPAGE_SETTINGS_INTRO_SEARCH_PARAMS } from 'Joyride/JoyridePayAtt.constants';\n\nimport { FullDivider, VerticalFlexBox } from 'generalComponents/BoxModifications';\nimport { introductionSelector } from 'store/selectors';\nimport { store } from 'store/store';\nimport { delay } from 'utils/utils';\nimport { introStepHomeSettings } from 'views/Home/JoyrideIntroduction/Settings/Home.JoyrideWidgetMenu.Steps';\nimport {\n INTRO_WIDGET_MENU_CLASS_NAMES,\n useHomeWidgetMenuJoyrideContext\n} from 'views/Home/JoyrideIntroduction/Settings/Home.JoyrideWidgetMenu.context';\nimport { HomeWidgetMenuJoyrideIntro } from 'views/Home/JoyrideIntroduction/Settings/Home.JoyrideWidgetMenuIntro';\n\nimport { useDashboardSelectionContext } from '../Contexts/DashboardSelectionContext/DashboardSelectionContext';\nimport { useGlobalSettingsContext } from '../Contexts/GlobalSettingsContext/GlobalSettingContext';\nimport { isFilterEqual } from '../Contexts/GlobalSettingsContext/utils';\nimport { GridItemKeys } from '../Interfaces/Dashboard.layout';\nimport { introStepDashboard } from '../JoyrideIntroductions/Dashboard.Joyride.Steps';\nimport { useDashboardJoyrideContext } from '../JoyrideIntroductions/Dashboard.Joyride.context';\nimport { SettingsMenu } from '../Widgets/MongoDBWidgets/ChartFilters/SettingsMenu';\nimport { HomepageSettingsTitle } from '../Widgets/MongoDBWidgets/ChartFilters/SettingsMenu.MenuTitles';\nimport { ChartTypes } from '../Widgets/MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.chartSpecification';\nimport {\n AllChartFilters,\n GLOBAL_FILTERS\n} from '../Widgets/MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.filters';\nimport { ChartStructure } from '../Widgets/MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.structure';\nimport { SelectBackgroundImage } from './GlobalSettings.SelectBackgroundImage';\nimport { DeleteDashboard } from './MenuSections/DangerZone/DangerZone.DeleteButton';\nimport { DashboardSettings } from './MenuSections/DashboardSettings/DashboardSettings';\nimport { ComponentMenuSection } from './MenuSections/WidgetMenuSection/WidgetMenuSection';\nimport { AccordionLabels } from './interfaces';\n\nconst MARGIN_THRESHOLD = 20;\n\n// Since we re-use the settings component for charts charts, we have to provide a specification. Since we don't have one\n// globaly, we create the below dummy spec.\n//\n// The filter array indicates which settings we should be able to filter on\nconst dummyChartSpecification: ChartStructure = {\n type: ChartTypes.BAR,\n filters: GLOBAL_FILTERS,\n defaultLayout: {\n i: '',\n layoutType: GridItemKeys.registrationsOverTime,\n requiresStampCardStats: false,\n x: 0,\n y: 0,\n w: 1,\n h: 1,\n minW: 0,\n minH: 0,\n maxW: 0,\n maxH: 0\n },\n MongoDBRefsAndRenderingSpec: {}\n};\n\nconst Styles = {\n Accordion: {\n Typography: { pb: '15px', mt: '10px', ml: '25.6px', flexGrow: '1', textAlign: 'center', fontWeight: 'normal' }\n }\n};\n\nexport const GlobalSettings: React.FC<{ homepage?: boolean }> = ({ homepage }) => {\n const { globalFilter, setGlobalFilter } = useGlobalSettingsContext();\n const { dashboard } = useDashboardSelectionContext();\n\n const {\n isRunning: isRunningDashboard,\n stepIndex,\n setStepIndex,\n openGlobalSettingsMenu\n } = useDashboardJoyrideContext();\n const {\n setIsRunning: setIsRunningHomeWidgetMenu,\n isRunning: isRunningHomeWidgetMenu,\n setIsManuallyStartedIntro\n } = useHomeWidgetMenuJoyrideContext();\n\n // Maintain a copy of the context here, in order to save upon closing the menue\n const [startDateSetting, setStartDateSetting] = useState(new Date(globalFilter.createdAt?.$gte));\n const [endDateSetting, setEndDateSetting] = useState(new Date(globalFilter.createdAt?.$lte));\n const [venueIdsSetting, setVenueIdsSetting] = useState(globalFilter.venueIdStr.$in);\n const [stampCardCampaignIdsSetting, setStampCardCampaignIdsSetting] = useState(\n globalFilter.stampCardCampaignIdStr.$in\n );\n const [trailingTimeFrameSetting, setTrailingTimeFrameSetting] = useState(\n globalFilter.trailingTimeFrame\n ? { unit: globalFilter.trailingTimeFrame.unit, value: globalFilter.trailingTimeFrame.value }\n : undefined\n );\n const [hourlyPeriodicSetting, setHourlyPeriodicSetting] = useState(globalFilter.hourlyPeriodic);\n const [dailyPeriodicSetting, setDailyPeriodicSetting] = useState(globalFilter.dailyPeriodic);\n const [foreverSetting, setForeverSetting] = useState(globalFilter.forever);\n\n const theme = useTheme();\n const [anchorEl, setAnchorEl] = useState(null);\n\n const settingsButton = useRef(null);\n\n // Only allow one accordion to be open at a time\n const [accordionExpanded, setAccordionExpanded] = useState(false);\n const handleAccordionClick = (label: AccordionLabels) => (_: React.SyntheticEvent, isExpanded: boolean) => {\n setAccordionExpanded(isExpanded ? label : false);\n };\n\n const updateContext = () => {\n const newFilter: AllChartFilters = {\n createdAt: { $gte: new Date(startDateSetting), $lte: new Date(endDateSetting) },\n forever: foreverSetting,\n trailingTimeFrame: trailingTimeFrameSetting\n ? { unit: trailingTimeFrameSetting.unit, value: trailingTimeFrameSetting.value }\n : undefined,\n dailyPeriodic: dailyPeriodicSetting,\n hourlyPeriodic: hourlyPeriodicSetting,\n stampCardCampaignIdStr: {\n $in: [...stampCardCampaignIdsSetting]\n },\n venueIdStr: {\n $in: [...venueIdsSetting]\n }\n };\n\n // Only update context if some field actually changed\n if (isFilterEqual(globalFilter, newFilter)) return;\n\n setGlobalFilter(newFilter);\n };\n\n const handleClose = (apply: boolean) => {\n if (apply) updateContext();\n\n setAnchorEl(null);\n };\n\n const handleClick = (event: React.MouseEvent) => {\n setAnchorEl(event.currentTarget);\n };\n\n const open = Boolean(anchorEl);\n\n useEffect(() => {\n const handle = async () => {\n if (openGlobalSettingsMenu && settingsButton.current) {\n setAnchorEl(settingsButton.current);\n await delay(100);\n setStepIndex((index) => index + 1);\n } else {\n setAnchorEl(null);\n }\n };\n\n handle();\n }, [openGlobalSettingsMenu, setStepIndex]);\n\n const settingsMenu = (\n \n );\n\n const closeMenuButton = () => {\n return (\n handleClose(false)}>\n \n \n );\n };\n\n const dashboardSettings = () => {\n if (!dashboard) return null;\n\n return (\n <>\n \n \n >\n );\n };\n\n const accordionFilterSection = (home: boolean) =>\n home ? (\n settingsMenu\n ) : (\n \n } className={introStepDashboard(8)}>\n \n \n \n \n {settingsMenu} \n \n );\n\n const componentMenuSection = () => (\n <>\n \n \n }>\n \n \n \n \n \n \n \n \n >\n );\n const dangerZone = () => {\n if (!dashboard) return null;\n\n return (\n <>\n \n \n \n \n >\n );\n };\n\n // Open settings modal to show intro if we were navigated here from the Introduction tab\n useEffect(() => {\n let timerIntro: NodeJS.Timeout | null = null;\n\n // If we have never seen the intro, show immediately\n const showIntro = introductionSelector(store.getState()).homepage.settings;\n if (showIntro) {\n setIsRunningHomeWidgetMenu(true);\n return;\n }\n\n const timer = setTimeout(() => {\n // Check if we were navigated here from the introduction tab\n if (window.location.search.includes(SHOW_HOMEPAGE_SETTINGS_INTRO_SEARCH_PARAMS)) {\n if (!settingsButton.current) return;\n\n // Set the anchorEl of the popup to be the button\n setAnchorEl(settingsButton.current);\n\n timerIntro = setTimeout(() => {\n setIsRunningHomeWidgetMenu(true);\n setIsManuallyStartedIntro(true);\n window.history.replaceState(null, '', window.location.pathname);\n }, JOYRIDE_TIMEOUT_MENU_OPEN);\n }\n }, JOYRIDE_TIMEOUT_MENU_OPEN);\n\n return () => {\n clearTimeout(timer);\n if (timerIntro) clearTimeout(timerIntro);\n };\n }, [setIsManuallyStartedIntro, setIsRunningHomeWidgetMenu]);\n\n const anchorElBottomLocation = anchorEl?.getBoundingClientRect().bottom || 0 + window.scrollX;\n const popoverMaxHeight =\n window.innerHeight < 575 ? `calc(100% - 20px)` : `calc(100% - ${MARGIN_THRESHOLD + anchorElBottomLocation}px)`;\n\n const showOnHomepage = homepage;\n const showOnDashboardGlobalSettings = !homepage;\n\n return (\n \n \n\n \n \n \n \n \n \n \n \n handleClose(false)}\n anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}\n transformOrigin={{ vertical: 'top', horizontal: 'right' }}\n className=\"payatt-widget-menu-popover\"\n transitionDuration={openGlobalSettingsMenu ? { enter: 0, exit: 0, appear: 0 } : 'auto'}>\n {/* Sections */}\n {showOnHomepage && (\n <>\n {closeMenuButton()}\n \n \n \n \n {accordionFilterSection(true)}\n >\n )}\n\n {showOnDashboardGlobalSettings && (\n <>\n {dashboardSettings()}\n {accordionFilterSection(false)}\n {componentMenuSection()}\n {dangerZone()}\n >\n )}\n \n \n );\n};\n","import { TrailingTimeFrame } from '../ChartFilters/Utils/DateSelector/DateSelector.interfaces';\nimport { AllowedMongoFilterSettings } from './MongoDBWidgets.interfaces.chartSpecification';\n\ntype FilterObject = { [key in AllowedMongoFilterSettings]: any };\n\nexport interface AllChartFilters extends Partial {\n forever: boolean;\n createdAt: { $gte: Date; $lte: Date };\n trailingTimeFrame: TrailingTimeFrame | undefined;\n dailyPeriodic: boolean;\n hourlyPeriodic: boolean;\n stampCardCampaignIdStr: { $in: string[] };\n venueIdStr: { $in: string[] };\n isFirst?: boolean;\n owner?: boolean;\n endpoint?: string[];\n}\n\nexport const GLOBAL_FILTERS: AllowedMongoFilterSettings[] = [\n 'forever',\n 'createdAt',\n 'trailingTimeframe',\n 'dailyPeriodic',\n 'hourlyPeriodic',\n 'venueId',\n 'stampCardCampaignId'\n];\n","import { ReactNode, createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { useGridItemContext } from 'views/Dashboard/Contexts/GridItemContext/GridItemContext';\nimport { DEFAULT_TRAILING_TIME_FRAME } from 'views/Dashboard/Widgets/MongoDBWidgets/ChartFilters/Utils/DateSelector/DateSelector.interfaces';\nimport { AllChartFilters } from 'views/Dashboard/Widgets/MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.filters';\n\nimport { useGlobalSettingsContext } from '../GlobalSettingsContext/GlobalSettingContext';\nimport { cloneChartFilter } from '../GlobalSettingsContext/utils';\nimport { ChartSettingsContextInterface, CreateChartSettingsFilterProps } from './ChartSettingsContext.interfaces';\n\nexport const ChartSettingsContext = createContext(null);\n\nconst createInitialFilter = ({ backendSavedFilter, globalFilter }: CreateChartSettingsFilterProps): AllChartFilters => {\n const filter: AllChartFilters = {\n // ##### Directly relates to MongoDB Chart filters\n createdAt: {\n $gte: new Date(backendSavedFilter?.createdAt?.$gte || globalFilter.createdAt.$gte),\n $lte: new Date(backendSavedFilter?.createdAt?.$lte || globalFilter.createdAt.$lte)\n },\n venueIdStr: {\n $in: backendSavedFilter?.venueIdStr?.$in\n ? [...backendSavedFilter.venueIdStr.$in]\n : [...globalFilter.venueIdStr.$in]\n },\n stampCardCampaignIdStr: {\n $in: backendSavedFilter?.stampCardCampaignIdStr?.$in\n ? [...backendSavedFilter.stampCardCampaignIdStr.$in]\n : [...globalFilter.stampCardCampaignIdStr.$in]\n },\n\n // ##### Not valid MongoDB Chart filters\n hourlyPeriodic: backendSavedFilter?.hourlyPeriodic || false,\n dailyPeriodic: backendSavedFilter?.dailyPeriodic || false,\n forever: backendSavedFilter?.forever || false,\n\n // Can be undefined\n trailingTimeFrame: backendSavedFilter?.trailingTimeFrame || DEFAULT_TRAILING_TIME_FRAME\n };\n\n return filter;\n};\n\nexport const ChartSettingsContextProvider: React.FC<{\n children: ReactNode;\n gridItemId: string;\n filter?: AllChartFilters;\n}> = ({ children, gridItemId, filter: backendSavedFilter }) => {\n const { globalFilter } = useGlobalSettingsContext();\n const { gridItems, updateGridItem } = useGridItemContext();\n\n const [filter, setFilter] = useState(createInitialFilter({ backendSavedFilter, globalFilter }));\n\n // Count all the useEffects to make sure that none of them are not run on first load, there are 2 useEffects\n const firstLoadCount = useRef(2);\n\n const firstLoadCheck = () => {\n if (firstLoadCount.current > 0) {\n firstLoadCount.current -= 1;\n return false;\n }\n\n return true;\n };\n\n // Update local filter with changes from the global filter\n useEffect(() => {\n if (!firstLoadCheck()) return;\n\n setFilter(cloneChartFilter(globalFilter));\n }, [globalFilter]);\n\n // Update global context gridItems when a chart filter is applied to have all information in one place, this\n // makes it easy to update the layouts in the DB when requested\n useEffect(() => {\n if (!firstLoadCheck()) return;\n\n const item = gridItems.find((el) => el.i === gridItemId);\n if (item) updateGridItem({ ...item, filter: cloneChartFilter(filter) });\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [filter, gridItemId, updateGridItem]);\n\n const value = useMemo(() => ({ filter, setFilter }), [filter]);\n\n return {children} ;\n};\n\nexport const useChartSettingsContext = () => {\n const chartSettingsContext = useContext(ChartSettingsContext);\n\n if (!chartSettingsContext) {\n throw new Error('No ChartSettingsContext.Provider found when calling useChartSettingsContext');\n }\n\n return chartSettingsContext;\n};\n","import { DragHandle } from '@mui/icons-material';\nimport Close from '@mui/icons-material/Close';\nimport { Box, IconButton, useTheme } from '@mui/material';\n\nimport { FlexBox } from 'generalComponents/BoxModifications';\n\nimport { CloseGridItem } from '../Widgets/helpers';\nimport { GridItemTopBarProps } from './ResponsiveGridLayout.interfaces';\n\nexport const GRID_ITEM_TOP_BAR_HEIGHT_INLINE = 30;\nexport const GRID_ITEM_TOP_BAR_HEIGHT = 45;\n\nexport const GridItemTopBar: React.FC = ({\n globalDragability,\n globalCloseability,\n removeGridItem,\n showTopBarInlineWithChartName,\n settingsButton,\n refreshStampCardDataButton\n}) => {\n const theme = useTheme();\n\n const homepageStyle = { position: 'absolute', right: '0' };\n\n return (\n \n {globalDragability && (\n \n \n \n )}\n \n {refreshStampCardDataButton}\n {settingsButton}\n {globalCloseability && (\n \n CloseGridItem(e, removeGridItem)}>\n \n \n \n )}\n \n );\n};\n","import React, { useEffect, useState } from 'react';\n\nimport SettingsIcon from '@mui/icons-material/Settings';\nimport { Box, IconButton, Popover, useTheme } from '@mui/material';\n\nimport { VerticalFlexBox } from 'generalComponents/BoxModifications';\nimport {\n ChartSettingsContextProvider,\n useChartSettingsContext\n} from 'views/Dashboard/Contexts/ChartSettingsContext/ChartSettingsContext';\nimport { useGlobalSettingsContext } from 'views/Dashboard/Contexts/GlobalSettingsContext/GlobalSettingContext';\nimport { isFilterFieldEqual } from 'views/Dashboard/Contexts/GlobalSettingsContext/utils';\nimport { useGridItemContext } from 'views/Dashboard/Contexts/GridItemContext/GridItemContext';\nimport { GridItemTopBar } from 'views/Dashboard/Grid/ResponsiveGridLayout.TopBar';\n\nimport { SettingsMenu } from './ChartFilters/SettingsMenu';\nimport { ChartWithSettingsProps } from './Interfaces/MongoDBWidgets.interfaces.props';\n\nconst ChartWithSettingsInner: React.FC = ({ chart, chartSpecification, layoutType }) => {\n const theme = useTheme();\n\n const { filter, setFilter } = useChartSettingsContext();\n const { globalDragability, globalCloseability } = useGlobalSettingsContext();\n const { removeGridItem } = useGridItemContext();\n\n // Maintain a copy of the context here, in order to save upon closing the menue\n const [startDateSetting, setStartDateSetting] = useState(new Date(filter.createdAt.$gte));\n const [endDateSetting, setEndDateSetting] = useState(new Date(filter.createdAt.$lte));\n const [hourlyPeriodicSetting, setHourlyPeriodicSetting] = useState(filter.hourlyPeriodic);\n const [dailyPeriodicSetting, setDailyPeriodicSetting] = useState(filter.dailyPeriodic);\n const [venueIdsSetting, setVenueIdsSetting] = useState([...filter.venueIdStr.$in]);\n const [stampCardCampaignIdsSetting, setStampCardCampaignIdsSetting] = useState([\n ...filter.stampCardCampaignIdStr.$in\n ]);\n const [trailingTimeFrameSetting, setTrailingTimeFrameSetting] = useState(\n typeof filter.trailingTimeFrame !== 'undefined'\n ? { unit: filter.trailingTimeFrame.unit, value: filter.trailingTimeFrame.value }\n : undefined\n );\n const [foreverSetting, setForeverSetting] = useState(filter.forever);\n\n const chartTopPadding = 10;\n const settingsButtonWidth = 30;\n const showTopBarInlineWithChartName = !globalCloseability && !globalDragability;\n\n const [anchorEl, setAnchorEl] = useState(null);\n\n const handleClick = (event: React.MouseEvent) => {\n setAnchorEl(event.currentTarget);\n };\n\n const updateContext = () => {\n setFilter((currFilter) => {\n const newFilter = { ...currFilter };\n\n if (!isFilterFieldEqual(startDateSetting, currFilter.createdAt.$gte)) {\n newFilter.createdAt.$gte = new Date(startDateSetting);\n }\n if (!isFilterFieldEqual(endDateSetting, currFilter.createdAt.$lte)) {\n newFilter.createdAt.$lte = new Date(endDateSetting);\n }\n if (!isFilterFieldEqual(trailingTimeFrameSetting, currFilter.trailingTimeFrame)) {\n newFilter.trailingTimeFrame = trailingTimeFrameSetting\n ? { unit: trailingTimeFrameSetting.unit, value: trailingTimeFrameSetting.value }\n : undefined;\n }\n if (!isFilterFieldEqual(hourlyPeriodicSetting, currFilter.hourlyPeriodic)) {\n newFilter.hourlyPeriodic = hourlyPeriodicSetting;\n }\n if (!isFilterFieldEqual(dailyPeriodicSetting, currFilter.dailyPeriodic)) {\n newFilter.dailyPeriodic = dailyPeriodicSetting;\n }\n if (!isFilterFieldEqual(foreverSetting, currFilter.forever)) {\n newFilter.forever = foreverSetting;\n }\n if (!isFilterFieldEqual(venueIdsSetting, currFilter.venueIdStr.$in)) {\n newFilter.venueIdStr.$in = [...venueIdsSetting];\n }\n if (!isFilterFieldEqual(stampCardCampaignIdsSetting, currFilter.stampCardCampaignIdStr.$in)) {\n newFilter.stampCardCampaignIdStr.$in = [...stampCardCampaignIdsSetting];\n }\n\n return newFilter;\n });\n };\n\n const resetValues = () => {\n setStartDateSetting(new Date(filter.createdAt.$gte));\n setEndDateSetting(new Date(filter.createdAt.$lte));\n setHourlyPeriodicSetting(filter.hourlyPeriodic);\n setDailyPeriodicSetting(filter.dailyPeriodic);\n setVenueIdsSetting(filter.venueIdStr.$in);\n setStampCardCampaignIdsSetting(filter.stampCardCampaignIdStr.$in);\n setTrailingTimeFrameSetting(\n typeof filter.trailingTimeFrame !== 'undefined'\n ? { unit: filter.trailingTimeFrame.unit, value: filter.trailingTimeFrame.value }\n : undefined\n );\n setForeverSetting(filter.forever);\n };\n\n const handleClose = (apply: boolean) => {\n if (apply) updateContext();\n else setTimeout(resetValues, 500);\n\n setAnchorEl(null);\n };\n\n // Update local settings when the global context is updated\n useEffect(() => {\n setStartDateSetting(new Date(filter.createdAt.$gte));\n setEndDateSetting(new Date(filter.createdAt.$lte));\n setTrailingTimeFrameSetting(\n filter.trailingTimeFrame\n ? { unit: filter.trailingTimeFrame.unit, value: filter.trailingTimeFrame.value }\n : undefined\n );\n setForeverSetting(filter.forever);\n setHourlyPeriodicSetting(filter.hourlyPeriodic);\n setDailyPeriodicSetting(filter.dailyPeriodic);\n setVenueIdsSetting([...filter.venueIdStr.$in]);\n setStampCardCampaignIdsSetting([...filter.stampCardCampaignIdStr.$in]);\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [filter]);\n\n const open = Boolean(anchorEl);\n\n const SettingsButton = (\n \n {\n if (showTopBarInlineWithChartName) return { p: 0 };\n return {};\n })()\n }}>\n \n \n handleClose(false)}\n anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}\n transformOrigin={{ vertical: 'top', horizontal: 'right' }}>\n \n \n \n );\n\n return (\n \n \n \n {React.cloneElement(chart, { chartSpecification })}\n \n \n \n );\n};\n\nexport const ChartWithSettings: React.FC = ({ gridItemId, filter, ...rest }) => (\n \n \n \n);\n","import {\n ChartRefAndRenderingSpecResponse,\n GetChartRefAndrenderingSpec,\n GetTimeVariantChartIdProps\n} from 'views/Dashboard/Interfaces/Dashboard.props';\n\nimport {\n ChartReferenceEnvironments,\n TimeBasedChartVariations\n} from '../Interfaces/MongoDBWidgets.interfaces.chartSpecification';\nimport { DefaultRenderSpecDescreteLine } from './ChartSpecification.MongoDBRenderingSpecs';\n\nconst MS = 1000;\nconst Hour = MS * 60 * 60;\nconst Day = Hour * 24;\nconst Week = Day * 7;\nconst Month = Day * 30; // Doesn't have to be precise, just a rough estimate\nconst Year = Day * 365;\n\nconst getType = (\n startDate: Date,\n endDate: Date,\n hourlyPeriodic: boolean,\n dailyPeriodic: boolean\n): TimeBasedChartVariations => {\n if (hourlyPeriodic) return 'hourlyPeriodic';\n if (dailyPeriodic) return 'dailyPeriodic';\n\n const timeDiff = endDate.getTime() - startDate.getTime();\n\n if (timeDiff <= Week) return 'hourly';\n if (timeDiff <= Month * 3) return 'daily';\n if (timeDiff <= Year) return 'weekly';\n if (timeDiff <= Year * 2) return 'monthly';\n\n return 'yearly';\n};\n\nconst getEnvironment = (): ChartReferenceEnvironments => {\n if (process.env.REACT_APP_ENV === 'staging') return 'staging';\n if (process.env.REACT_APP_ENV === 'development') return 'dev';\n\n return 'prod';\n};\n\nconst emptyResponse = { renderingSpec: { version: DefaultRenderSpecDescreteLine.version }, ref: '' };\n\nexport const getTimeVariantChartInfo = ({\n chartSpecification,\n locale,\n startDate,\n endDate,\n hourlyPeriodic,\n dailyPeriodic\n}: GetTimeVariantChartIdProps): ChartRefAndRenderingSpecResponse => {\n const allChartIds =\n chartSpecification.MongoDBRefsAndRenderingSpec[getType(startDate, endDate, hourlyPeriodic, dailyPeriodic)];\n if (!allChartIds) return emptyResponse;\n\n const renderingSpec = allChartIds.renderingSpec[locale];\n const ref = allChartIds[getEnvironment()];\n\n if (typeof ref !== 'string') return emptyResponse;\n\n return { renderingSpec, ref };\n};\n\nexport const getChartId = ({\n chartSpecification,\n locale\n}: GetChartRefAndrenderingSpec): ChartRefAndRenderingSpecResponse => {\n const allChartIds = chartSpecification.MongoDBRefsAndRenderingSpec.default;\n if (!allChartIds) return emptyResponse;\n\n const renderingSpec = allChartIds.renderingSpec[locale];\n const ref = allChartIds[getEnvironment()];\n\n if (typeof ref !== 'string') return emptyResponse;\n\n return { renderingSpec, ref };\n};\n","import React, { useEffect, useRef, useState } from 'react';\n\nimport { Box } from '@mui/material';\nimport Backdrop from '@mui/material/Backdrop';\n\nimport { Chart as EmbedChart, EmbedChartOptions } from '@mongodb-js/charts-embed-dom';\n\nimport NoContent from 'generalComponents/Status/NoContent/NoContent';\nimport { useGlobalSettingsContext } from 'views/Dashboard/Contexts/GlobalSettingsContext/GlobalSettingContext';\n\nimport { ChartProps } from './Interfaces/MongoDBWidgets.interfaces.props';\n\nconst DefaultChartProps: Omit = {\n height: '100%',\n width: '100%',\n theme: 'light',\n showAttribution: false\n};\n\nconst ChartWrapperStyle = { position: 'relative', height: '100%', width: '100%' };\n\nconst ChartStyle = { width: '100%', height: '100%' };\n\nconst NoContentBackdrop: React.FC<{ show: boolean }> = ({ show }) => {\n return (\n \n \n \n );\n};\n\nconst FirstLoadBackdrop: React.FC<{ show: boolean }> = ({ show }) => {\n return (\n \n \n \n );\n};\n\nexport const Chart = ({ filter, chartId, renderingSpec }: ChartProps) => {\n const { chartSDK } = useGlobalSettingsContext();\n\n const chartDiv = useRef(null);\n const [rendered, setRendered] = useState(false);\n\n const [chart, setChart] = useState(undefined);\n\n useEffect(() => {\n setRendered(false);\n }, [chartId]);\n\n useEffect(() => {\n if (chartSDK) {\n setChart(chartSDK.createChart({ ...DefaultChartProps, chartId, filter, renderingSpec }));\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [chartId, chartSDK]);\n\n useEffect(() => {\n const displayChart = async () => {\n if (chartDiv.current && chart) {\n try {\n await chart.render(chartDiv.current);\n setRendered(true);\n } catch (err: any) {\n console.error('Error during Charts rendering.', err);\n }\n }\n };\n\n displayChart();\n }, [chart]);\n\n useEffect(() => {\n const updateFilter = async () => {\n if (rendered && chart) {\n try {\n await chart.setFilter(filter);\n } catch (err: any) {\n console.error('Error while filtering', err);\n }\n }\n };\n\n updateFilter();\n }, [chart, rendered, filter]);\n\n useEffect(() => {\n const updateRenderingSpec = async () => {\n if (rendered && chart) {\n try {\n await chart.setRenderingSpecOverride(renderingSpec);\n } catch (err: any) {\n console.error('Error while updating rendering spec', err);\n }\n }\n };\n\n updateRenderingSpec();\n }, [chart, rendered, renderingSpec]);\n\n if (!chart) return null;\n\n return (\n \n \n \n \n \n );\n};\n","import LocaleAPI from 'store/locale';\nimport { useChartSettingsContext } from 'views/Dashboard/Contexts/ChartSettingsContext/ChartSettingsContext';\n\nimport { ChartSpecification } from './ChartSpecification/ChartSpecification';\nimport { getChartId, getTimeVariantChartInfo } from './ChartSpecification/ChartSpecification.GetChartId';\nimport { ChartPresetPerCountry } from './Interfaces/MongoDBWidgets.interfaces.chartSpecification';\nimport { AllChartFilters } from './Interfaces/MongoDBWidgets.interfaces.filters';\nimport { ChartStructure } from './Interfaces/MongoDBWidgets.interfaces.structure';\nimport { Chart } from './MongoDBWidgets.Chart';\n\nconst setupFilters = (chartSpecification: ChartStructure, contextFilter: AllChartFilters) => {\n const allowedFilters: RecursivePartial = {};\n\n if (chartSpecification.filters.includes('createdAt')) allowedFilters.createdAt = contextFilter.createdAt;\n if (chartSpecification.filters.includes('stampCardCampaignId'))\n allowedFilters.stampCardCampaignIdStr = contextFilter.stampCardCampaignIdStr;\n if (chartSpecification.filters.includes('venueId')) allowedFilters.venueIdStr = contextFilter.venueIdStr;\n\n return allowedFilters;\n};\n\nexport const GenerateChartFromContext: React.FC<{\n layoutType: keyof typeof ChartSpecification;\n chartSpecification?: ChartStructure;\n chartWithTimeVariant?: boolean;\n}> = ({ layoutType, chartSpecification, chartWithTimeVariant }) => {\n const { filter } = useChartSettingsContext();\n\n const locale = LocaleAPI() as unknown as keyof ChartPresetPerCountry;\n\n if (!chartSpecification) {\n console.error('Missing chartSpecification');\n return null;\n }\n\n const { ref, renderingSpec } = chartWithTimeVariant\n ? getTimeVariantChartInfo({\n chartSpecification,\n locale,\n startDate: filter.createdAt.$gte,\n endDate: filter.createdAt.$lte,\n hourlyPeriodic: filter.hourlyPeriodic,\n dailyPeriodic: filter.dailyPeriodic\n })\n : getChartId({ chartSpecification, locale });\n\n const allowedFilters = setupFilters(chartSpecification, filter);\n\n return ;\n};\n","import { useRef } from 'react';\n\nimport { Box, SxProps } from '@mui/material';\n\nimport { GRID_ITEM_TOP_BAR_HEIGHT } from 'views/Dashboard/Grid/ResponsiveGridLayout.TopBar';\nimport { useContainerRect } from 'views/Dashboard/Grid/ResponsiveGridLayout.useContainerRect';\n\nimport { imageSpec } from './Helpers/Calculations.Interfaces';\n\nexport const CalculationBackgroundImage: React.FC<{\n showItemTopBar?: boolean;\n image: Required;\n children: React.ReactNode;\n}> = ({ showItemTopBar, image, children }) => {\n const marginTopWrapper = showItemTopBar ? -GRID_ITEM_TOP_BAR_HEIGHT + 4 : 0;\n\n const ref = useRef(null);\n const containerRect = useContainerRect(ref);\n\n const wrapperBoxStyle = { mt: `${marginTopWrapper}px`, height: '100%' };\n const backgroundImageStyle: SxProps = { objectFit: 'cover', position: 'absolute', zIndex: '1' };\n\n if (!image.width || !image.height) {\n console.error('Missing image width and height');\n return null;\n }\n\n const aspectRatioImg = image.width / image.height;\n const aspectRatioContainer = containerRect.width / containerRect.height;\n\n if (aspectRatioImg > aspectRatioContainer) backgroundImageStyle.height = '100%';\n else backgroundImageStyle.width = '100%';\n\n return (\n \n \n {children}\n \n );\n};\n","import { useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Box, Button, Popover, SxProps, Typography } from '@mui/material';\n\nimport { CenteredFlexBox } from 'generalComponents/BoxModifications';\nimport { addCancelGridDragabilityToPopups } from 'views/Dashboard/Grid/ResponsiveGridLayout.utils';\n\nexport const InfoButton: React.FC<{\n children: React.ReactNode;\n buttonTransStr?: string;\n buttonVariant?: 'text' | 'outlined' | 'contained';\n buttonSx?: SxProps;\n}> = ({ children, buttonTransStr, buttonVariant, buttonSx }) => {\n const [anchorEl, setAnchorEl] = useState(null);\n\n const handleClick = (event: React.MouseEvent) => {\n addCancelGridDragabilityToPopups();\n setAnchorEl(event.currentTarget);\n };\n\n const handleClose = () => {\n setAnchorEl(null);\n };\n\n const open = Boolean(anchorEl);\n const popoverId = open ? 'info-popover' : undefined;\n\n return (\n <>\n \n \n \n \n \n \n \n \n \n \n \n \n {children}\n \n \n >\n );\n};\n","import { TextField, styled } from '@mui/material';\n\nexport const TextFieldNumericStyled = styled(TextField)(\n () => `\n input::-webkit-outer-spin-button,\n input::-webkit-inner-spin-button {\n opacity: 1;\n }\n\n /* Firefox */\n input[type=number] {\n opacity: 1;\n }\n`\n);\n","import { useEffect, useState } from 'react';\n\nimport { Box, MenuItem, Select, SelectChangeEvent } from '@mui/material';\n\nimport { TextFieldNumericStyled } from './Calculations.styledComponents';\nimport { EMPHASIZED_TEXT_FONT_SIZE } from './Calculations.utils';\n\ninterface InlineNumericTextFieldProps {\n value: string;\n setValue: (val: string) => void;\n emph?: boolean;\n maxValue?: number;\n minValue?: number;\n step?: string;\n fontSize?: number;\n allowEditing?: boolean;\n}\n\ninterface InlineSelectTextFieldProps {\n value: string;\n setValue: (val: string) => void;\n items: { key: string; value: React.ReactNode }[];\n allowEditing?: boolean;\n}\n\nexport const InlineNumericTextField = ({\n value,\n setValue,\n step,\n maxValue,\n minValue,\n emph = false,\n fontSize = 11,\n allowEditing = true\n}: InlineNumericTextFieldProps) => {\n const defaultInputWidth = 33;\n const max = 99999999;\n const min = 0;\n\n const [inputWidth, setInputWidth] = useState(defaultInputWidth);\n\n // Make sure the width of the input field is inline with the text\n useEffect(() => {\n if (value.length * fontSize > 30) {\n setInputWidth((value.length + 1) * fontSize);\n } else {\n setInputWidth(defaultInputWidth);\n }\n }, [fontSize, value]);\n\n const setValueWithStep: React.ChangeEventHandler | undefined = (e) => {\n const newVal = e.target.value;\n\n // Sanity check, test if number\n if (!/^[0-9]*(\\.[0-9][0-9]*)?$/.test(newVal)) return;\n const newValAsNumber = parseFloat(newVal);\n\n if (Number.isNaN(newValAsNumber)) return minValue;\n\n if (newValAsNumber < 0) return;\n if (newValAsNumber > 9999999) return;\n\n if (step && newValAsNumber > 1) return;\n if ((typeof maxValue === 'number' && newValAsNumber > maxValue) || newValAsNumber > max) return;\n if ((typeof minValue === 'number' && newValAsNumber < minValue) || newValAsNumber < min) return;\n\n setValue(newVal);\n };\n\n if (!allowEditing) {\n return (\n \n {value}\n \n );\n }\n\n return (\n \n );\n};\n\nexport const InlineSelectTextField = ({ value, setValue, items, allowEditing = true }: InlineSelectTextFieldProps) => {\n const setSelectedValue = (e: SelectChangeEvent) => {\n setValue(e.target.value);\n };\n\n if (!allowEditing) {\n const item = items.find((el) => el.key === value);\n return {item?.value} ;\n }\n\n return (\n \n {items.map((item) => {\n return (\n \n {item.value}\n \n );\n })}\n \n );\n};\n","import { useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Box, ListItem, Typography } from '@mui/material';\n\nimport { VerticalFlexBox } from 'generalComponents/BoxModifications';\nimport { allVenueRegistrationStats } from 'store/selectors';\nimport { store } from 'store/store';\n\nimport { CalculationBackgroundImage } from './Calculations.BackgroundImage';\nimport { InfoButton } from './Helpers/Calculations.InfoButton';\nimport { InlineNumericTextField } from './Helpers/Calculations.InlineTextField';\nimport { CalculationProps, CustomizableValuesSMSCampaign } from './Helpers/Calculations.Interfaces';\nimport { EmphBoxNumeric, MEMBER_THRESHOLD, useLiveData } from './Helpers/Calculations.utils';\n\nconst DEFAULT_VALUES: CustomizableValuesSMSCampaign = {\n numOfMembers: MEMBER_THRESHOLD,\n averagePurchase: 100,\n recipientRatio: 33\n};\n\nconst calculateValues = ({ numOfMembers, averagePurchase, recipientRatio }: CustomizableValuesSMSCampaign) => {\n const recipients = Math.round(numOfMembers * (recipientRatio / 100));\n const revenueIncrease = averagePurchase * recipients;\n\n return { recipients, revenueIncrease };\n};\n\nexport const IncreasedRevenueBasedOnSMSCampaign: React.FC = ({ showItemTopBar }) => {\n const stats = allVenueRegistrationStats(store.getState());\n const members = stats.data.reduce((acc, ven) => acc + ven.totalCustomers, 0);\n\n const liveData = useLiveData(members);\n\n const [numOfMembers, setNumOfMembers] = useState(liveData ? members : DEFAULT_VALUES.numOfMembers);\n const [averagePurchase, setAveragePurchase] = useState(DEFAULT_VALUES.averagePurchase);\n const [recipientRatio, setRecipientRatio] = useState(DEFAULT_VALUES.recipientRatio);\n\n const { revenueIncrease } = calculateValues({\n numOfMembers,\n averagePurchase,\n recipientRatio\n });\n\n const editStr = liveData ? 'editingAllowed' : 'editingDisabled';\n\n const setValue = (val: string, key: keyof CustomizableValuesSMSCampaign) => {\n switch (key) {\n case 'numOfMembers':\n setNumOfMembers(parseInt(val, 10));\n break;\n case 'averagePurchase':\n setAveragePurchase(parseInt(val, 10));\n break;\n case 'recipientRatio':\n default:\n setRecipientRatio(parseInt(val, 10));\n break;\n }\n };\n\n return (\n \n \n {members >= MEMBER_THRESHOLD ? (\n \n {' '}\n {members}{' '}\n \n \n ) : (\n \n \n \n )}\n \n \n \n {' '}\n setValue(val, 'recipientRatio')}\n maxValue={100}\n allowEditing\n minValue={0}\n />\n %{' '}\n = MEMBER_THRESHOLD ? 'above120' : 'below120'\n }`}\n />{' '}\n kr \n {' '}\n setValue(val, 'averagePurchase')}\n allowEditing\n />{' '}\n kr.{' '}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n","import { Box } from '@mui/material';\n\nexport const SideImage: React.FC<{ backgroundImagePath: string }> = ({ backgroundImagePath }) => {\n return (\n \n \n \n );\n};\n","import { Box } from '@mui/material';\n\nimport { CenteredFlexBox, FlexBox, VerticalFlexBox } from 'generalComponents/BoxModifications';\nimport { GRID_ITEM_TOP_BAR_HEIGHT } from 'views/Dashboard/Grid/ResponsiveGridLayout.TopBar';\n\nimport { InfoButton } from './Helpers/Calculations.InfoButton';\nimport { CalculationWrapperProps } from './Helpers/Calculations.Interfaces';\nimport { SideImage } from './Helpers/Calculations.SideImage';\n\nexport const CalculationSideImage: React.FC = ({\n showItemTopBar,\n image,\n calculationBox,\n titleTextBox,\n infoButtonContent\n}) => {\n const marginTopWrapper = showItemTopBar ? -GRID_ITEM_TOP_BAR_HEIGHT + 4 : 0;\n const titlePaddingTop = showItemTopBar ? 10 : 2;\n\n const wrapperBoxStyle = { mt: `${marginTopWrapper}px`, height: '100%' };\n\n return (\n \n \n \n {titleTextBox} \n {calculationBox} \n {infoButtonContent} \n \n \n );\n};\n","import { VenueCategories } from 'store/features/venueCategories/handlers';\nimport { venuesSelector } from 'store/selectors';\nimport { store } from 'store/store';\n\ntype URL = string;\n\nconst getRandomImage = (images: URL[]): URL => {\n return images[Math.floor(Math.random() * images.length)];\n};\n\nconst categoryToVisitImgMap: Record = {\n Pizzeria: ['guy-serving-coffee.webp', 'restaurant-pizzeria.webp'],\n Bar: ['guy-serving-coffee.webp'],\n BarRestaurant: ['guy-serving-coffee.webp', 'restaurant-general-2.webp'],\n Restaurant: ['guy-serving-coffee.webp', 'restaurant-general-2.webp', 'restaurant-general-3.webp'],\n ThaiRestaurant: ['guy-serving-coffee.webp', 'restaurant-general-2.webp'],\n IndianRestaurant: ['guy-serving-coffee.webp', 'restaurant-general-2.webp'],\n BurgerRestaurant: ['guy-serving-coffee.webp', 'restaurant-general-2.webp', 'restaurant-general-3.webp'],\n ItalianRestaurant: ['guy-serving-coffee.webp', 'restaurant-general-2.webp', 'restaurant-general-3.webp'],\n SushiRestaurant: ['guy-serving-coffee.webp', 'restaurant-general-2.webp'],\n FlowerShop: ['flower-shop-2.webp', 'flower-shop-3.webp'],\n PetStore: ['guy-serving-coffee.webp'],\n Cafe: ['guy-serving-coffee.webp'],\n BagelCafe: ['guy-serving-coffee.webp'],\n FishStore: ['guy-serving-coffee.webp'],\n MeatStore: ['guy-serving-coffee.webp'],\n YarnStore: ['clothes-store.webp'],\n DeliStore: ['guy-serving-coffee.webp'],\n FastFoodGrill: ['guy-serving-coffee.webp'],\n CandyStore: ['guy-serving-coffee.webp'],\n HealthyStore: ['beauty-allround.webp'],\n ClothingStore: ['clothes-store.webp', 'clothes-store-4.webp', 'clothes-store-5.webp'],\n Lifestyle: ['beauty-allround.webp'],\n BeautySalon: ['beauty-allround.webp'],\n IcecreamShop: ['guy-serving-coffee.webp'],\n FoodTruckHealthyFood: ['guy-serving-coffee.webp'],\n WineImporter: ['guy-serving-coffee.webp'],\n Hairdresser: ['hairdresser-barbershop.webp'],\n HairdresserBeauty: ['beauty-hairdresser.webp'],\n Barber: ['hairdresser-barbershop.webp'],\n Other: ['guy-serving-coffee.webp'],\n Default: ['guy-serving-coffee.webp']\n};\n\nconst categoryToSCImgMap: Record = {\n Pizzeria: ['woman-with-croissant.webp', 'restaurant-pizzeria-2.webp'],\n Bar: ['woman-with-croissant.webp'],\n BarRestaurant: ['woman-with-croissant.webp'],\n Restaurant: ['woman-with-croissant.webp', 'restaurant-general.webp'],\n ThaiRestaurant: ['woman-with-croissant.webp'],\n IndianRestaurant: ['woman-with-croissant.webp'],\n BurgerRestaurant: ['woman-with-croissant.webp'],\n ItalianRestaurant: ['woman-with-croissant.webp'],\n SushiRestaurant: ['woman-with-croissant.webp'],\n FlowerShop: ['flower-shop.webp'],\n PetStore: ['woman-with-croissant.webp'],\n Cafe: ['woman-with-croissant.webp'],\n BagelCafe: ['woman-with-croissant.webp'],\n FishStore: ['woman-with-croissant.webp'],\n MeatStore: ['woman-with-croissant.webp'],\n YarnStore: ['clothes-store-2.webp'],\n DeliStore: ['woman-with-croissant.webp'],\n FastFoodGrill: ['woman-with-croissant.webp'],\n CandyStore: ['woman-with-croissant.webp'],\n HealthyStore: ['beauty-salon.webp', 'store-general.webp'],\n ClothingStore: ['clothes-store-2.webp', 'store-general.webp', 'clothes-store-3.webp'],\n Lifestyle: ['beauty-salon.webp', 'store-general.webp'],\n BeautySalon: ['beauty-salon.webp', 'store-general.webp'],\n IcecreamShop: ['woman-with-croissant.webp', 'store-general.webp'],\n FoodTruckHealthyFood: ['woman-with-croissant.webp'],\n WineImporter: ['woman-with-croissant.webp', 'store-general.webp'],\n Hairdresser: ['barbershop.webp'],\n HairdresserBeauty: ['beauty-hairdresser-2.webp'],\n Barber: ['barbershop.webp'],\n Other: ['woman-with-croissant.webp', 'store-general.webp'],\n Default: ['woman-with-croissant.webp', 'store-general.webp']\n};\n\nexport const getImageUrl = (type: 'stampCard' | 'visit') => {\n const venues = venuesSelector(store.getState());\n const allCategories = venues.map((el) => el.category as keyof typeof VenueCategories);\n\n if (!allCategories.length) console.warn(`No venue categories found, using default image`);\n\n // Since we can only show 1 image in the portal, but have multiple venues, we select one category at random\n const venueCategory = allCategories[Math.floor(Math.random() * allCategories.length)];\n\n const name =\n type === 'stampCard'\n ? getRandomImage(categoryToSCImgMap[venueCategory || 'Default'])\n : getRandomImage(categoryToVisitImgMap[venueCategory || 'Default']);\n\n const folder = type === 'stampCard' ? 'stampCardCalculation' : 'visitCalculation';\n\n return `/static/images/widgets/${folder}/${name}`;\n};\n","import { useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Box, ListItem, Typography } from '@mui/material';\n\nimport { allVenueRegistrationStats } from 'store/selectors';\nimport { store } from 'store/store';\n\nimport { CalculationSideImage } from './Calculation.SideImageCalclulation';\nimport { getImageUrl } from './Helpers/Calculations.GetImageUrl';\nimport { InlineNumericTextField } from './Helpers/Calculations.InlineTextField';\nimport { CalculationProps, CustomizableValuesStampCardCampaign } from './Helpers/Calculations.Interfaces';\nimport { EmphBoxNumeric, MEMBER_THRESHOLD, useLiveData } from './Helpers/Calculations.utils';\n\nconst DEFAULT_VALUES: CustomizableValuesStampCardCampaign = {\n numOfMembers: MEMBER_THRESHOLD,\n averagePurchase: 200,\n numOfPurchases: 1,\n timespan: 'Monthly'\n};\n\nconst calculateValues = ({\n numOfMembers,\n averagePurchase,\n numOfPurchases\n}: Omit) => {\n const revenue = numOfMembers * numOfPurchases * averagePurchase;\n\n const revenueIncrease = { '12': Math.round(revenue * 0.12), '18': Math.round(revenue * 0.18) };\n\n return revenueIncrease;\n};\n\nconst Calculation: React.FC<{ members: number }> = ({ members }) => {\n const liveData = useLiveData(members);\n\n const [numOfMembers, setNumOfMembers] = useState(liveData ? members : DEFAULT_VALUES.numOfMembers);\n const [averagePurchase, setAveragePurchase] = useState(DEFAULT_VALUES.averagePurchase);\n const [numOfPurchases, setNumOfPurchases] = useState(DEFAULT_VALUES.numOfPurchases);\n\n const revenueIncrease = calculateValues({ numOfMembers, averagePurchase, numOfPurchases });\n\n const setValue = (val: string, key: keyof CustomizableValuesStampCardCampaign) => {\n switch (key) {\n case 'numOfMembers':\n setNumOfMembers(parseInt(val, 10));\n break;\n case 'averagePurchase':\n setAveragePurchase(parseInt(val, 10));\n break;\n case 'numOfPurchases':\n default:\n setNumOfPurchases(parseInt(val, 10));\n }\n };\n\n return (\n \n \n {' '}\n 12-18% \n \n \n \n \n {' '}\n \n - \n {' '}\n kr {' '}\n {' '}\n setValue(val, 'numOfMembers')}\n allowEditing\n minValue={120}\n />{' '}\n {' '}\n {' '}\n setValue(val, 'averagePurchase')}\n allowEditing\n />{' '}\n kr{' '}\n \n \n \n );\n};\n\nexport const IncreasedRevenueBasedOnStampCardCampaign: React.FC = ({ showItemTopBar }) => {\n const stats = allVenueRegistrationStats(store.getState());\n const members = stats.data.reduce((acc, ven) => acc + ven.totalCustomers, 0);\n\n const editStr = useLiveData(members) ? 'editingAllowed' : 'editingDisabled';\n\n return (\n }\n infoButtonContent={\n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n *\n \n \n \n \n \n *\n \n \n \n \n \n }\n titleTextBox={\n \n \n \n }\n showItemTopBar={showItemTopBar}\n />\n );\n};\n","import { useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Box, ListItem, Typography } from '@mui/material';\n\nimport { allVenueRegistrationStats } from 'store/selectors';\nimport { store } from 'store/store';\n\nimport { CalculationSideImage } from './Calculation.SideImageCalclulation';\nimport { getImageUrl } from './Helpers/Calculations.GetImageUrl';\nimport { InlineNumericTextField, InlineSelectTextField } from './Helpers/Calculations.InlineTextField';\nimport { CalculationProps, CustomizableValuesVisits, Timespan } from './Helpers/Calculations.Interfaces';\nimport { EmphBoxNumeric, MEMBER_THRESHOLD, useLiveData } from './Helpers/Calculations.utils';\n\nconst DEFAULT_VALUES: CustomizableValuesVisits = {\n numOfMembers: MEMBER_THRESHOLD,\n averagePurchase: 100,\n numOfPurchases: 1,\n margin: 60,\n timespan: 'Quarterly'\n};\n\nconst calculateValues = ({\n numOfMembers,\n margin,\n averagePurchase,\n numOfPurchases\n}: Omit) => {\n const revenueIncrease = averagePurchase * numOfMembers * numOfPurchases;\n\n const earning = Math.round(revenueIncrease * (margin / 100));\n\n return { revenueIncrease, earning };\n};\n\nconst Calculation: React.FC<{ members: number }> = ({ members }) => {\n const liveData = useLiveData(members);\n\n const [numOfMembers, setNumOfMembers] = useState(liveData ? members : DEFAULT_VALUES.numOfMembers);\n const [averagePurchase, setAveragePurchase] = useState(DEFAULT_VALUES.averagePurchase);\n const [numOfPurchases, setNumOfPurchases] = useState(DEFAULT_VALUES.numOfPurchases);\n const [margin, setMargin] = useState(DEFAULT_VALUES.margin);\n const [timespan, setTimespan] = useState(DEFAULT_VALUES.timespan);\n\n const { earning, revenueIncrease } = calculateValues({ numOfMembers, averagePurchase, margin, numOfPurchases });\n\n const timespanSelectItems = [\n {\n key: 'Quarterly',\n value: (\n \n )\n },\n {\n key: 'Yearly',\n value: (\n \n )\n }\n ];\n\n const setValue = (val: string, key: keyof CustomizableValuesVisits) => {\n switch (key) {\n case 'numOfMembers':\n setNumOfMembers(parseInt(val, 10));\n break;\n case 'averagePurchase':\n setAveragePurchase(parseInt(val, 10));\n break;\n case 'timespan':\n setTimespan(val as Timespan);\n break;\n case 'numOfPurchases':\n setNumOfPurchases(parseInt(val, 10));\n break;\n case 'margin':\n default:\n setMargin(parseFloat(val));\n }\n };\n\n return (\n \n \n {' '}\n setValue(e, 'numOfPurchases')}\n minValue={1}\n allowEditing\n emph={false}\n />{' '}\n 1 ? 'times' : 'time'\n }.${timespan}`}\n />{' '}\n setValue(e, 'timespan')}\n items={timespanSelectItems}\n />{' '}\n {' '}\n kr \n {' '}\n kr {' '}\n \n !\n \n \n \n {' '}\n setValue(val, 'numOfMembers')}\n minValue={120}\n />{' '}\n {' '}\n setValue(val, 'averagePurchase')}\n allowEditing\n emph={false}\n />{' '}\n kr {' '}\n setValue(val, 'margin')}\n maxValue={100}\n allowEditing\n emph={false}\n />\n %.\n \n \n );\n};\n\nexport const IncreasedRevenueBasedOnVisits: React.FC = ({ showItemTopBar }) => {\n const stats = allVenueRegistrationStats(store.getState());\n const members = stats.data.reduce((acc, ven) => acc + ven.totalCustomers, 0);\n\n const editStr = useLiveData(members) ? 'editingAllowed' : 'editingDisabled';\n\n return (\n }\n infoButtonContent={\n \n \n \n \n \n }\n titleTextBox={\n \n \n \n }\n showItemTopBar={showItemTopBar}\n />\n );\n};\n","import { useEffect, useState } from 'react';\nimport { I18n } from 'react-redux-i18n';\n\nimport { Refresh } from '@mui/icons-material';\nimport { Box, CircularProgress, IconButton, Tooltip, useTheme } from '@mui/material';\n\nimport { VerticalCenteredFlexBox, VerticalFlexBox } from 'generalComponents/BoxModifications';\nimport { useStampCardCampaignStatsContext } from 'views/Dashboard/Contexts/StampCardCampaignStatsContext/StampCardCampaignStatsContext';\nimport { GridItemTopBar } from 'views/Dashboard/Grid/ResponsiveGridLayout.TopBar';\nimport { GridItemKeys } from 'views/Dashboard/Interfaces/Dashboard.layout';\nimport { useDashboardJoyrideContext } from 'views/Dashboard/JoyrideIntroductions/Dashboard.Joyride.context';\nimport { introStepHome } from 'views/Home/JoyrideIntroduction/Main/Home.Joyride.Steps';\n\nimport { useGlobalSettingsContext } from '../../Contexts/GlobalSettingsContext/GlobalSettingContext';\nimport { useGridItemContext } from '../../Contexts/GridItemContext/GridItemContext';\n\nexport const RefreshStampCardDataButton = () => {\n const theme = useTheme();\n\n const { updateStats: updateStampCardStats } = useStampCardCampaignStatsContext();\n const [loading, setLoading] = useState(false);\n\n const handleClick = async () => {\n if (loading) return;\n\n setLoading(true);\n await updateStampCardStats();\n setLoading(false);\n };\n\n return (\n \n {loading && (\n \n \n \n )}\n {!loading && (\n \n \n \n \n \n )}\n \n );\n};\n\nexport const GridItemWrapper: React.FC<{\n children: React.ReactNode;\n layoutType: keyof typeof GridItemKeys;\n refreshStampCardStatsButton?: boolean;\n showItemTopBar?: boolean;\n}> = ({ layoutType, children, refreshStampCardStatsButton, showItemTopBar = true }) => {\n const { globalDragability, globalCloseability } = useGlobalSettingsContext();\n const { isRunning, stepIndex, setStepIndex } = useDashboardJoyrideContext();\n\n const { removeGridItem } = useGridItemContext();\n\n // We need to set the zIndex dynamically as we can't pass it into react-grid-layout as a prop\n useEffect(() => {\n setTimeout(() => {\n const resizeHandles = document.querySelectorAll('.react-resizable-handle') as NodeListOf;\n\n for (let i = 0; i < resizeHandles.length; i += 1) {\n resizeHandles[i].style.zIndex = '10';\n }\n }, 200);\n });\n\n const removeItem = (i: string) => {\n // Automatically continue to the next intro step if we are doing the walkthrough intro\n if (isRunning) {\n setStepIndex(stepIndex + 1);\n }\n\n removeGridItem(i);\n };\n\n const showTopBarInlineWithChartName =\n showItemTopBar && !globalCloseability && !globalDragability && !refreshStampCardStatsButton;\n\n const homepageIntroStep =\n window.location.href.includes('home') && layoutType === 'sendSMSCampaign' ? introStepHome(6) : '';\n\n return (\n \n {showItemTopBar && (\n : undefined\n }\n removeGridItem={removeItem}\n showTopBarInlineWithChartName={showTopBarInlineWithChartName}\n />\n )}\n {children}\n \n );\n};\n","import { Translate } from 'react-redux-i18n';\n\nimport { Typography, useTheme } from '@mui/material';\n\nimport { VerticalCenteredFlexBox } from 'generalComponents/BoxModifications';\nimport { useStampCardCampaignStatsContext } from 'views/Dashboard/Contexts/StampCardCampaignStatsContext/StampCardCampaignStatsContext';\n\nexport const NumberOfStampsAndStampCards = () => {\n const theme = useTheme();\n const { stats } = useStampCardCampaignStatsContext();\n\n const stamps = stats?.reduce((acc, curr) => acc + curr.totalVisits, 0) || 0;\n const stampCards = stats?.reduce((acc, curr) => acc + curr.totalClients, 0) || 0;\n\n return (\n \n \n \n \n \n );\n};\n","import { useEffect } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Button, Tooltip, Typography } from '@mui/material';\n\nimport { CenteredFlexBox } from 'generalComponents/BoxModifications';\nimport { UrlShortener } from 'generalComponents/UrlShortener/UrlShortener';\nimport { SMSBox } from 'views/IntelliSms/SMSBox';\nimport { SMSBoxFilters } from 'views/IntelliSms/dialogs/Filters/Filters';\nimport { INTELLI_SMS_BASE_Z_INDEX } from 'views/IntelliSms/dialogs/Filters/interfaces';\nimport { SavedSuggestionsDialog } from 'views/IntelliSms/dialogs/SavedSuggestions';\nimport { SMSCampaignSummary } from 'views/IntelliSms/dialogs/Summary/Summary';\nimport { useThreeBoxes } from 'views/IntelliSms/helperHooks/useThreeBoxes';\nimport { ActiveBoxes } from 'views/IntelliSms/helpers/interfaces';\nimport { ScheduleCalendar } from 'views/IntelliSms/newCampaign/NewCampaign.ScheduleCalendar';\nimport { NewCampaignMenu } from 'views/IntelliSms/newCampaign/NewCampaign.menu';\n\nexport const SendSMSCampaign = () => {\n const {\n suggestionsLoading,\n messageBoxOpacity,\n box1State,\n updateBox1State,\n debounced,\n switchActiveBox,\n activeBox,\n editTextDialogNext,\n loadSmsCampaignWidget,\n menuArgs,\n savedSuggestionsOpen,\n closeSavedSuggestionsDialog,\n urlShortenerDialogOpen,\n closeUrlShortenerDialog,\n openMenu\n } = useThreeBoxes(false);\n\n useEffect(() => {\n loadSmsCampaignWidget();\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <>\n \n \n \n \n \n \n \n \n \n \n\n \n\n \n }>\n \n \n \n \n \n \n \n >\n );\n};\n","import { Card } from '@mui/material';\n\nimport styled from '@emotion/styled';\n\nexport const StyledGridItem = styled(Card)(\n () => `\n backgroundColor: #ffffff;\n padding: 0px;\n margin: 0px;\n cursor: pointer;\n`\n);\n\nexport const StyledGridItemHighlighted = styled(Card)(\n () => `\n backgroundColor: #ffffff;\n padding: 0px;\n margin: 0px;\n cursor: pointer;\n box-shadow: 0px 9px 16px #1bf4032e, 0px 2px 2px #1bf4032e;\n border: 1px solid #1bf4032e;\n`\n);\n","import { ChartSpecification } from '../Widgets/MongoDBWidgets/ChartSpecification/ChartSpecification';\nimport { TimeBasedCharts } from '../Widgets/MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.names';\nimport { ChartWithSettings } from '../Widgets/MongoDBWidgets/MongoDBWidgets.ChartWithSettings';\nimport { GenerateChartFromContext } from '../Widgets/MongoDBWidgets/MongoDBWidgets.GenerateChartFromContext.';\nimport { IncreasedRevenueBasedOnSMSCampaign } from '../Widgets/PayAttWidgets/Calculations/IncreasedRevenueBasedOnSMSCampaign';\nimport { IncreasedRevenueBasedOnStampCardCampaign } from '../Widgets/PayAttWidgets/Calculations/IncreasedRevenueBasedOnStampCardCampaign';\nimport { IncreasedRevenueBasedOnVisits } from '../Widgets/PayAttWidgets/Calculations/IncreasedRevenueBasedOnVisits';\nimport { GridItemWrapper } from '../Widgets/PayAttWidgets/PayAttWidgets.GridItemWrapper';\nimport { PayAttWidgetNames } from '../Widgets/PayAttWidgets/PayAttWidgets.names';\nimport { NumberOfStampsAndStampCards } from '../Widgets/PayAttWidgets/Sentences/NumberOfStampsAndStampCards';\nimport { SendSMSCampaign } from '../Widgets/PayAttWidgets/SmsCampaign/SendSMSCampaign';\nimport { ChartItemProps, CreateGridItemProps, CreatePayAttComponentProps } from './ResponsiveGridLayout.interfaces';\nimport { StyledGridItem, StyledGridItemHighlighted } from './Styling/StyledGridItems';\n\nconst CreatePayAttComponent = ({ gridItemLayout, showItemTopBar = true }: CreatePayAttComponentProps) => {\n const gridData = {\n key: gridItemLayout.i,\n className: 'react-grid-item grid-item',\n 'data-grid': gridItemLayout,\n 'data-grid-id': gridItemLayout.i\n };\n\n if (gridItemLayout.layoutType === PayAttWidgetNames.sendSMSCampaign) {\n return (\n \n \n \n \n \n );\n }\n\n if (gridItemLayout.layoutType === PayAttWidgetNames.numberOfStampsAndStampCardsSentence) {\n return (\n \n \n \n \n \n );\n }\n\n const component = (() => {\n switch (gridItemLayout.layoutType) {\n case PayAttWidgetNames.increasedRevenueBasedOnSMSCampaign:\n return ;\n case PayAttWidgetNames.increasedRevenueBasedOnVisits:\n return ;\n case PayAttWidgetNames.increasedRevenueBasedOnStampCardCampaign:\n return ;\n default:\n console.error(\n `No PayAtt component found for the layoutType ${gridItemLayout.layoutType}. Available widgets are: ${PayAttWidgetNames}`\n );\n return null;\n }\n })();\n\n return (\n \n \n {component || }\n \n \n );\n};\n\nconst ChartItem = ({ gridItemLayout, chart, chartStructure }: ChartItemProps) => {\n return (\n \n \n \n );\n};\n\nexport const CreateGridItem = ({ gridItemLayout, venueIds, showItemTopBar }: CreateGridItemProps) => {\n const layoutType = gridItemLayout.layoutType as keyof typeof ChartSpecification;\n\n if (Object.keys(PayAttWidgetNames).includes(layoutType)) {\n return CreatePayAttComponent({ gridItemLayout, venueIds, showItemTopBar });\n }\n\n if (Object.keys(TimeBasedCharts).includes(layoutType)) {\n return ChartItem({\n gridItemLayout,\n chartStructure: ChartSpecification[layoutType],\n chart: \n });\n }\n\n return ChartItem({\n gridItemLayout,\n chartStructure: ChartSpecification[layoutType],\n chart: \n });\n};\n","import { I18n } from 'react-redux-i18n';\nimport { toast } from 'react-toastify';\n\nimport { DASHBOARD_VALID_NAME_REG_EXP, MAX_NUMBER_OF_DASHBOARDS } from 'api/dashboard/interface';\nimport { isPayAttError } from 'errorHandling/payattError';\nimport {\n createDashboard as createDashboardAction,\n deleteDashboard as deleteDashboardAction,\n updateDashboard,\n updateDashboardName\n} from 'store/features/dashboard/actions';\nimport { DashboardDoc } from 'store/features/dashboard/handlers';\n\nimport {\n CreateDashboardLayoutProps,\n DeleteDashboardLayoutProps,\n SaveDashboardLayoutProps,\n SaveDashboardNameProps\n} from '../Interfaces/Dashboard.props';\n\nconst successToast = (message: string) => {\n toast.success(message, { autoClose: 5000 });\n};\n\nconst errorToast = (message: string) => {\n toast.error(message, { autoClose: 10000 });\n};\n\nexport const saveDashboard = async ({\n gridItems,\n layout,\n dashboard,\n breakpoint,\n dispatch\n}: SaveDashboardLayoutProps) => {\n const allItems = gridItems.map((el) => {\n const item = layout.find((el2) => el2.i === el.i);\n if (item) return { ...el, ...item };\n return { ...el };\n });\n\n const { payload } = await dispatch(updateDashboard({ id: dashboard.id, layout: allItems, breakpoint }));\n\n if (!payload || isPayAttError(payload)) {\n return errorToast(I18n.t('dashboard.toasts.dashboardNotUpdated'));\n }\n\n if (payload.status >= 200 || payload.status < 300) {\n return successToast(I18n.t('dashboard.toasts.dashboardUpdated', { name: dashboard.name }));\n }\n\n errorToast(I18n.t('dashboard.toasts.dashboardNotUpdated'));\n};\n\nexport const saveDashboardName = async ({ dashboardId, name, dispatch }: SaveDashboardNameProps): Promise => {\n if (!DASHBOARD_VALID_NAME_REG_EXP.test(name)) {\n errorToast(I18n.t('dashboard.toasts.invalidDashboardName', { name }));\n return false;\n }\n\n const response = await dispatch(updateDashboardName({ id: dashboardId, name }));\n\n const status = (response.payload as any).status as number;\n if (status >= 200 || status < 300) {\n successToast(I18n.t('dashboard.toasts.dashboardNameUpdated', { name }));\n return true;\n }\n\n errorToast(I18n.t('dashboard.toasts.dashboardNameNotUpdated'));\n return false;\n};\n\nexport const createDashboard = async ({\n name,\n breakpoint,\n layout,\n dispatch\n}: CreateDashboardLayoutProps): Promise => {\n try {\n const { payload } = await dispatch(createDashboardAction({ name, layout, breakpoint }));\n\n if (isPayAttError(payload)) {\n if (payload.status === 409) {\n errorToast(\n I18n.t('dashboard.toasts.maximumNumberOfDashboardsReached', {\n maximumNumberOfDashboards: MAX_NUMBER_OF_DASHBOARDS\n })\n );\n } else {\n throw new Error('error');\n }\n } else if (payload) {\n successToast(I18n.t('dashboard.toasts.dashboardCreated', { name }));\n return payload;\n } else {\n errorToast(I18n.t('dashboard.toasts.unknownError'));\n }\n } catch (err) {\n errorToast(I18n.t('dashboard.toasts.dashboardNotCreated'));\n }\n};\n\nexport const deleteDashboard = async ({ id, name, dispatch }: DeleteDashboardLayoutProps): Promise => {\n try {\n const { payload } = await dispatch(deleteDashboardAction({ id }));\n\n if (!payload) throw new Error('Unknown error');\n\n if (!payload || isPayAttError(payload)) {\n errorToast(I18n.t('dashboard.toasts.dashboardNotDeleted', { name }));\n return false;\n }\n\n if (payload.status >= 200 || payload.status < 300) {\n successToast(I18n.t('dashboard.toasts.dashboardDeleted', { name }));\n return true;\n }\n\n throw Error('Unknown error');\n } catch (err) {\n errorToast(I18n.t('dashboard.toasts.unknownError'));\n return false;\n }\n};\n","import React, { useContext, useEffect, useState } from 'react';\n\nimport { useDebouncedCallback } from 'use-debounce';\n\nimport { SidebarContext } from 'contexts/SidebarContext';\n\nconst EMPTY_DOM_RECT = {\n height: 0,\n width: 0,\n x: 0,\n y: 0,\n bottom: 0,\n left: 0,\n right: 0,\n top: 0,\n toJSON: () => {\n // do nothing\n }\n};\n\nexport const useContainerRect = (containerRef: React.RefObject, initialRect?: DOMRect) => {\n const [rect, setRect] = useState(initialRect || EMPTY_DOM_RECT);\n const { toggleSidebar } = useContext(SidebarContext);\n\n const handleResizeDebounce = useDebouncedCallback(() => {\n if (containerRef.current) {\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width !== rect?.width || height !== rect?.height) {\n setRect(containerRef.current.getBoundingClientRect());\n }\n }\n }, 500);\n\n // ###### First render, start an observer on the resize event. We need this to re-calculate the container size\n useEffect(() => {\n let observer: ResizeObserver;\n let timer: NodeJS.Timeout;\n\n const startObserver = (timeout: number, count: number, max: number) => {\n if (timer) clearTimeout(timer);\n if (count > max) return;\n\n if (containerRef.current) {\n observer = new ResizeObserver(() => {\n handleResizeDebounce();\n // handleResize();\n });\n observer.observe(containerRef.current);\n } else {\n timer = setTimeout(() => {\n startObserver(timeout, count + 1, max);\n }, timeout);\n }\n };\n\n timer = setTimeout(() => {\n startObserver(500, 1, 20);\n }, 500);\n\n return () => {\n if (timer) clearTimeout(100);\n if (observer) observer.disconnect();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // ###### Create a delayed resize event to update the container width when the sidebar is toggled\n useEffect(() => {\n const timer = setTimeout(() => {\n if (containerRef?.current) {\n setRect(containerRef.current.getBoundingClientRect());\n window.dispatchEvent(new Event('resize'));\n }\n }, 500);\n\n return () => clearTimeout(timer);\n }, [containerRef, toggleSidebar]);\n\n return rect;\n};\n","import { delay } from 'utils/utils';\n\nimport { GRID_MARGIN, GRID_ROW_HEIGHT } from '../Interfaces/Dashboard.constants';\n\nexport const cellStrikeColor = '#cccccc31';\nexport const cellStrikeColorEditing = '#cccccc';\n\nexport const addCancelGridDragabilityToPopups = async (delayTime?: number) => {\n // Wait for backdrop to load\n await delay(delayTime || 500);\n\n const backdrops = document.querySelectorAll(\n '.MuiPopover-root:not(.cancelGridDragability), .MuiPickersPopper-root:not(.cancelGridDragability)'\n );\n backdrops.forEach((backdrop) => backdrop.classList.add('cancelGridDragability'));\n};\n\nexport const calculateGridSize = ({ cols, containerWidth }: { cols: number; containerWidth: number }) => {\n const marginSlotsCount = cols - 1;\n const totalHorizontalMargin = marginSlotsCount * GRID_MARGIN[0];\n const freeSpace = containerWidth - totalHorizontalMargin;\n\n return {\n width: freeSpace / cols,\n height: GRID_ROW_HEIGHT\n };\n};\n\nexport const generateGridBackground = ({\n cellSize,\n cols,\n gridWidth,\n cellStrokeColor,\n margin = GRID_MARGIN\n}: {\n cellSize: { width: number; height: number };\n cols: number;\n gridWidth: number;\n cellStrokeColor: string;\n margin?: [number, number];\n}) => {\n const XMLNS = 'http://www.w3.org/2000/svg';\n const [horizontalMargin, verticalMargin] = margin;\n const rowHeight = cellSize.height + verticalMargin;\n\n const y = 0;\n const w = cellSize.width;\n const h = cellSize.height;\n\n const rectangles: string[] = [];\n\n for (let i = 0; i < cols; i += 1) {\n const x = i * (cellSize.width + horizontalMargin);\n rectangles.push(\n ` `\n );\n }\n\n const svg = [``, ...rectangles, ` `].join('');\n\n return `url(\"data:image/svg+xml;utf8,${encodeURIComponent(svg)}\")`;\n};\n","import { DashboardBreakpoint } from './Dashboard.breakpoints';\n\nexport const GRID_MARGIN: [number, number] = [10, 10]; // [width, height]\n\nexport const GRID_ROW_HEIGHT = 30;\n\nexport const RESPONSIVE_COLUMNS: { [key in DashboardBreakpoint]: number } = { xl: 30, lg: 26, md: 20, sm: 16, xs: 10 };\n\nexport const DefaultGridSettings = { globalDragability: true, globalResizability: true, globalCloseability: true };\n","import { Step } from 'react-joyride';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Lightbulb } from '@mui/icons-material';\nimport { Box } from '@mui/material';\n\nimport { CreateIntroStep, DashboardMainIntroStepLength } from 'Joyride/JoyridePayAtt.constants';\n\nimport { CenteredFlexBox } from 'generalComponents/BoxModifications';\n\ntype DashboardIntroSteps = Tuple;\n\nconst createIntroStep = (count: number, step?: CreateIntroStep): Step => {\n return {\n ...step,\n disableBeacon: true,\n disableScrolling: step?.disableScrolling || true,\n disableScrollParentFix: step?.disableScrollParentFix || false,\n target: `.dashboard-intro-step-${count}`,\n title:\n step?.title === null\n ? undefined\n : step?.title || ,\n content:\n step?.content === null\n ? undefined\n : step?.content || \n };\n};\n\nexport const dashboardIntroSteps: DashboardIntroSteps = [\n createIntroStep(1, {\n placement: 'center',\n spotlightPadding: 25,\n styles: { tooltip: { width: '800px', maxWidth: '80%' }, tooltipContent: { textAlign: 'left' } },\n content: (\n \n \n \n )\n }),\n createIntroStep(2, { placement: 'right', styles: { tooltipContent: { textAlign: 'left' } } }),\n createIntroStep(3, {\n placement: 'right',\n styles: { tooltipContent: { textAlign: 'left' } },\n spotlightClicks: true,\n content: \n }),\n createIntroStep(4, { placement: 'right', styles: { tooltipContent: { textAlign: 'left' } } }),\n createIntroStep(5, {\n placement: 'right',\n styles: { tooltipContent: { textAlign: 'left' } },\n spotlightPadding: 0,\n spotlightClicks: true,\n content: \n }),\n createIntroStep(6, {\n placement: 'right',\n spotlightPadding: 0,\n spotlightClicks: true,\n styles: { tooltipContent: { textAlign: 'left' } },\n content: \n }),\n createIntroStep(7, {\n placement: 'right',\n styles: { tooltipContent: { textAlign: 'left' } },\n content: \n }),\n createIntroStep(8, {\n placement: 'right',\n styles: { tooltipContent: { textAlign: 'left' }, options: { width: '500px' } },\n content: \n }),\n createIntroStep(9, {\n placement: 'right',\n styles: { tooltipContent: { textAlign: 'left' } },\n content: \n }),\n createIntroStep(10, { placement: 'right', styles: { tooltipContent: { textAlign: 'left' } } }),\n createIntroStep(11, {\n placement: 'right',\n styles: { tooltipContent: { textAlign: 'left' } },\n spotlightClicks: true,\n spotlightPadding: 0,\n content: \n }),\n createIntroStep(12, {\n placement: 'left',\n styles: { tooltipContent: { textAlign: 'left' } },\n content: (\n \n \n \n \n \n \n )\n }),\n createIntroStep(13, {\n placement: 'center',\n styles: { tooltipContent: { textAlign: 'left' } },\n content: \n })\n];\n\n// Start at 1, just like JoyRide step array\nexport const introStepDashboard = (val: number) => {\n const { target } = dashboardIntroSteps[val - 1];\n\n if (typeof target === 'string') return target.substring(1);\n\n throw new Error('Cannot call introStepDashboard on a step with a target set to JSX.Element');\n};\n","import { createContext, useCallback, useContext, useMemo, useState } from 'react';\nimport { ACTIONS, CallBackProps, EVENTS, STATUS } from 'react-joyride';\n\ninterface DashboardJoyrideContext {\n stepIndex: number;\n setStepIndex: React.Dispatch>;\n isRunning: boolean;\n setIsRunning: React.Dispatch>;\n refreshTooltip: (timeout?: number) => void;\n handleJoyrideCallback: (data: CallBackProps) => void;\n openDashboardsMenu: boolean;\n openGlobalSettingsMenu: boolean;\n openSaveMenu: boolean;\n}\n\nconst refreshTooltip = (timeout?: number) => {\n const time = timeout || 0;\n setTimeout(() => window.dispatchEvent(new Event('resize')), time);\n};\n\nexport const DashboardJoyrideContext = createContext(null);\nexport const DashboardJoyrideContextProvider: React.FC<{\n children: React.ReactNode;\n}> = ({ children }) => {\n const [stepIndex, setStepIndex] = useState(0);\n const [isRunning, setIsRunning] = useState(false);\n\n const [openDashboardsMenu, setOpenDashboardsMenu] = useState(false);\n const [openGlobalSettingsMenu, setOpenGlobalSettingsMenu] = useState(false);\n const [openSaveMenu, setOpenSaveMenu] = useState(false);\n\n const handleJoyrideCallback = useCallback(async (data: CallBackProps) => {\n const { action, index, type, status } = data;\n\n if (EVENTS.STEP_AFTER === type) {\n if (ACTIONS.NEXT === action) {\n // Programmatically advance index after menu has opened\n if (index === 1) return setOpenDashboardsMenu(true);\n\n if (index === 2) setOpenDashboardsMenu(false);\n\n // Programmatically advance index after menu has opened\n if (index === 3) return setOpenGlobalSettingsMenu(true);\n\n if (index === 9) setOpenGlobalSettingsMenu(false);\n\n // Programmatically advance index after menu has opened\n if (index === 10) return setOpenSaveMenu(true);\n\n if (index === 11) setOpenSaveMenu(false);\n }\n\n if (ACTIONS.PREV === action) {\n if (index === 2) setOpenDashboardsMenu(false);\n\n if (index === 3) {\n // The function listening to the variable openDashboardMenu advances the index with 1, hence we must set it to be 1 less than what we want\n setStepIndex(1);\n return setOpenDashboardsMenu(true);\n }\n\n if (index === 4) setOpenGlobalSettingsMenu(false);\n\n if (index === 10) return setStepIndex(3);\n\n if (index === 11) setOpenSaveMenu(false);\n\n if (index === 12) {\n // The function listening to the variable openSaveMenu advances the index with 1, hence we must set it to be 1 less than what we want\n setStepIndex(10);\n return setOpenSaveMenu(true);\n }\n }\n }\n\n // Handle what to do when the tour is finished or skipped:\n if ([STATUS.FINISHED, STATUS.SKIPPED].includes(status as any)) {\n // Need to set our running state to false, so we can restart if we click start again.\n setIsRunning(false);\n setStepIndex(0);\n return;\n }\n\n // Handle what to do when a Joyride button is clicked (eg 'Next', close, 'Back', etc), or if a Joyride button is clicked and the target for the next step doesn't exist.\n if ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND].includes(type as any)) {\n const newIndex = index + (action === ACTIONS.PREV ? -1 : 1);\n setStepIndex(newIndex);\n }\n }, []);\n\n const value = useMemo(\n () => ({\n stepIndex,\n setStepIndex,\n isRunning,\n setIsRunning,\n refreshTooltip,\n handleJoyrideCallback,\n openDashboardsMenu,\n openGlobalSettingsMenu,\n openSaveMenu\n }),\n [stepIndex, isRunning, handleJoyrideCallback, openDashboardsMenu, openGlobalSettingsMenu, openSaveMenu]\n );\n\n return {children} ;\n};\n\nexport const useDashboardJoyrideContext = () => {\n const dashboardJoyrideContext = useContext(DashboardJoyrideContext);\n\n if (!dashboardJoyrideContext) {\n throw new Error('No DashboardJoyrideContext.Provider found when calling useDashboardJoyrideContext');\n }\n\n return dashboardJoyrideContext;\n};\n","import { Translate } from 'react-redux-i18n';\n\nimport { InfoOutlined } from '@mui/icons-material';\nimport { Box, Tooltip, Typography, useTheme } from '@mui/material';\n\nimport { CenteredFlexBox, FlexBox, FullDivider } from 'generalComponents/BoxModifications';\nimport { getTextWidth } from 'utils/utils';\nimport { introStepHomeSettings } from 'views/Home/JoyrideIntroduction/Settings/Home.JoyrideWidgetMenu.Steps';\n\nexport const HomepageSettingsTitle = () => {\n const theme = useTheme();\n\n return (\n \n \n \n \n \n \n\n \n }>\n \n \n \n \n \n \n \n \n \n );\n};\n\nexport const ChartSettingsFilterTitle: React.FC<{ name: string | undefined }> = ({ name }) => {\n const theme = useTheme();\n\n const textWidth = name\n ? getTextWidth(name, `${theme.typography.h3.fontSize}px ${theme.typography.h3.fontFamily}`)\n : -1;\n\n const nameComponent = () => (\n \n {name || }\n \n );\n\n const text = () => {\n return textWidth > 330 ? (\n \n {nameComponent()}\n \n ) : (\n nameComponent()\n );\n };\n\n return (\n \n {text()}\n \n \n );\n};\n\nexport const HomepageGeneralTitle = () => {\n return (\n \n \n \n \n \n );\n};\n","/* eslint-disable @typescript-eslint/no-unused-vars */\nimport React from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Button } from '@mui/material';\n\nimport { FlexBox, FullDivider } from 'generalComponents/BoxModifications';\nimport { introStepHomeSettings } from 'views/Home/JoyrideIntroduction/Settings/Home.JoyrideWidgetMenu.Steps';\nimport { INTRO_WIDGET_MENU_CLASS_NAMES } from 'views/Home/JoyrideIntroduction/Settings/Home.JoyrideWidgetMenu.context';\n\nexport const ApplySettingsButton: React.FC<{ closeMenu: (apply: boolean) => void; localSettingsMenu?: boolean }> = ({\n closeMenu,\n localSettingsMenu\n}) => {\n return (\n <>\n {localSettingsMenu && }\n \n closeMenu(true)}>\n \n \n \n >\n );\n};\n","import React from 'react';\nimport { Translate, TranslateProps } from 'react-redux-i18n';\n\nimport { Box, ListItem, ListItemProps, Tooltip, Typography, TypographyProps } from '@mui/material';\n\nimport { VerticalFlexBox } from 'generalComponents/BoxModifications';\n\nexport const TitleText: React.FC<{\n title: string;\n typographyProps?: TypographyProps;\n translateProps?: Partial;\n}> = ({ title, typographyProps = { variant: 'h4' }, translateProps = {} }) => {\n const { variant, ...rest } = typographyProps;\n\n return (\n \n \n \n );\n};\n\nexport const FilterRow: React.FC<{\n title?: string;\n typographyProps?: TypographyProps;\n translateProps?: Partial;\n tooltip?: string;\n listItemProps?: ListItemProps;\n children?: React.ReactNode;\n}> = ({ title, typographyProps, translateProps, tooltip, listItemProps, children }) => {\n const { sx: listItemSx, ...listItemRest } = listItemProps || {};\n\n const titleWithTooltip = () => {\n if (!title) return null;\n\n if (tooltip) {\n return (\n } placement=\"top\">\n \n \n \n \n );\n }\n\n return ;\n };\n\n return (\n \n \n {titleWithTooltip()}\n {children}\n \n \n );\n};\n","import React from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Container, Typography } from '@mui/material';\n\nimport { CenteredFlexBox } from 'generalComponents/BoxModifications';\n\nexport const NoFilterSettings = () => {\n return (\n \n \n \n \n \n \n \n );\n};\n","import { useEffect, useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Box } from '@mui/material';\nimport { DatePicker, LocalizationProvider } from '@mui/x-date-pickers';\nimport { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';\nimport { svSE } from '@mui/x-date-pickers/locales';\n\nimport { FlexBox, VerticalFlexBox } from 'generalComponents/BoxModifications';\n\nimport { addCancelGridDragabilityToPopups } from '../../../../../Grid/ResponsiveGridLayout.utils';\nimport { MAX_RANGE_IN_DAYS } from './DateSelector.constants';\nimport { newDateZeroSecond } from './DateSelector.helpers';\nimport { DateRangeProps, Errors } from './DateSelector.interfaces';\n\nexport const DateRangePicker: React.FC = ({\n startDate,\n setStartDate,\n endDate,\n setEndDate,\n disabled: disabledProp = false,\n maxRangeInDays = MAX_RANGE_IN_DAYS\n}) => {\n const [error, setError] = useState(Errors.Nothing);\n const [startDateInner, setStartDateInner] = useState(startDate);\n const [endDateInner, setEndDateInner] = useState(endDate);\n\n const getDayDiff = (start: Date, end: Date): number => {\n const dayDiff = (end.getTime() - start.getTime()) / 1000 / 60 / 60 / 24;\n\n return dayDiff;\n };\n\n useEffect(() => {\n setStartDateInner(startDate);\n }, [startDate]);\n\n useEffect(() => {\n setEndDateInner(endDate);\n }, [endDate]);\n\n const today = newDateZeroSecond();\n\n const shouldDisableStartDate = (start: Date): boolean => {\n return start.getTime() > endDate.getTime();\n };\n\n const shouldDisableEndDate = (end: Date): boolean => {\n return end.getTime() > today.getTime() || end.getTime() < startDate.getTime();\n };\n\n useEffect(() => {\n if (endDateInner) {\n if (getDayDiff(startDateInner, endDateInner) <= maxRangeInDays) {\n setStartDate(startDateInner);\n setEndDate(endDateInner);\n setError(Errors.Nothing);\n } else {\n setError(Errors.MaxLength);\n }\n } else {\n setStartDate(startDateInner);\n }\n }, [startDateInner, endDateInner, maxRangeInDays, setStartDate, setEndDate]);\n\n const DatePickerStyle = {\n width: '200px',\n '.MuiInputBase-root': {\n border: error === Errors.Nothing ? 'none' : '1px solid red'\n }\n };\n\n const startDateSelector = () => (\n }\n onChange={(value) => {\n if (value) setStartDateInner(value);\n }}\n format=\"yyyy/MM/dd\"\n value={startDateInner}\n shouldDisableDate={shouldDisableStartDate}\n disabled={disabledProp}\n onError={(err) => {\n console.error('error in datePicker', err);\n }}\n onOpen={() => addCancelGridDragabilityToPopups()}\n />\n );\n\n const endDateSelector = () => (\n }\n onChange={(value) => {\n if (value) setEndDateInner(value);\n }}\n shouldDisableDate={shouldDisableEndDate}\n format=\"yyyy/MM/dd\"\n value={endDateInner}\n disabled={disabledProp}\n onError={(err) => {\n console.error('error in datePicker', err);\n }}\n onOpen={() => addCancelGridDragabilityToPopups()}\n />\n );\n\n return (\n \n \n \n {startDateSelector()}\n {endDateSelector()}\n \n \n {error !== Errors.Nothing && (\n \n \n \n )}\n \n );\n};\n","import { useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Button, Checkbox, Typography } from '@mui/material';\n\nimport { introStepHomeSettings } from 'views/Home/JoyrideIntroduction/Settings/Home.JoyrideWidgetMenu.Steps';\n\nimport { newDateZeroSecond, newForeverStartDate } from './DateSelector.helpers';\nimport { ForeverButtonProps } from './DateSelector.interfaces';\n\nexport const ForeverButton: React.FC = ({\n checked,\n setChecked,\n startDate,\n setStartDate,\n endDate,\n setEndDate,\n setTrailingTimeFrame\n}) => {\n const [oldStartDate] = useState(startDate);\n const [oldEndDate] = useState(endDate);\n\n const onClick = () => {\n if (!checked) {\n setStartDate(newForeverStartDate());\n setEndDate(newDateZeroSecond());\n\n // When selecting to show data from all time, we should remove the trailing time frame settings and stick to\n // normal dates\n setTrailingTimeFrame(undefined);\n } else {\n setStartDate(oldStartDate);\n setEndDate(oldEndDate);\n }\n\n setChecked(!checked);\n };\n\n return (\n \n \n \n \n \n \n );\n};\n","import { useCallback, useEffect, useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { MenuItem, Select } from '@mui/material';\n\nimport { CenteredFlexBox } from 'generalComponents/BoxModifications';\n\nimport { addCancelGridDragabilityToPopups } from '../../../../../Grid/ResponsiveGridLayout.utils';\nimport { getStartDateFromValAndUnit, getTimeFrameRange, newDateZeroSecond } from './DateSelector.helpers';\nimport { TrailingDateRangeProps, UnitValues, unitRange } from './DateSelector.interfaces';\n\nexport const TrailingDateRangePicker: React.FC = ({\n trailingTimeFrame,\n setTrailingTimeFrame,\n setStartDate,\n setEndDate,\n disabled = false\n}) => {\n const [unit, setUnit] = useState(trailingTimeFrame.unit);\n const [selectedValue, setSelectedValue] = useState(trailingTimeFrame.value);\n const [valueOptions, setValueOptions] = useState(getTimeFrameRange(trailingTimeFrame.unit));\n\n const [firstLoad, setFirstLoad] = useState(2);\n\n const setNewDates = useCallback(() => {\n if (!valueOptions.includes(selectedValue)) return;\n\n setStartDate(getStartDateFromValAndUnit(unit, selectedValue));\n setEndDate(newDateZeroSecond());\n }, [selectedValue, setEndDate, setStartDate, unit, valueOptions]);\n\n const checkFirstLoad = () => {\n if (firstLoad > 0) {\n setFirstLoad((el) => el - 1);\n return true;\n }\n\n return false;\n };\n\n useEffect(() => {\n if (checkFirstLoad()) return;\n\n setValueOptions(getTimeFrameRange(unit));\n setNewDates();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [unit]);\n\n useEffect(() => {\n if (checkFirstLoad()) return;\n\n if (!valueOptions.includes(selectedValue)) return setSelectedValue(valueOptions[0]);\n\n setTrailingTimeFrame({ unit, value: selectedValue });\n setNewDates();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selectedValue, unit, valueOptions]);\n\n return (\n \n addCancelGridDragabilityToPopups()}\n onChange={(e) => setSelectedValue(+e.target.value)}>\n {valueOptions.map((el) => (\n \n {el}\n \n ))}\n \n addCancelGridDragabilityToPopups()}\n onChange={(e) => setUnit(e.target.value as UnitValues)}>\n {unitRange.map((el) => (\n \n \n \n ))}\n \n \n );\n};\n","import { useEffect } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Button } from '@mui/material';\n\nimport { introStepHomeSettings } from 'views/Home/JoyrideIntroduction/Settings/Home.JoyrideWidgetMenu.Steps';\n\nimport { DateRangePicker } from './DateSelector.DateRangePicker';\nimport { ForeverButton } from './DateSelector.ForeverButton';\nimport { TrailingDateRangePicker } from './DateSelector.TrailingDateRangePicker';\nimport { calculateTrailingTimeFrame, getStartDateFromValAndUnit } from './DateSelector.helpers';\nimport { SelectTypeOfDateRangePickerProps } from './DateSelector.interfaces';\n\nexport const SelectTypeOfDateRangePicker: React.FC = ({\n trailingTimeFrame,\n setTrailingTimeFrame,\n showTrailingTimeSelector,\n showDatePickers,\n showForeverButton,\n startDate,\n setStartDate,\n forever,\n setForever,\n ...rest\n}) => {\n const onClick = () => {\n if (typeof trailingTimeFrame === 'undefined') {\n setTrailingTimeFrame(calculateTrailingTimeFrame(startDate));\n } else {\n setTrailingTimeFrame(undefined);\n }\n };\n\n const data: JSX.Element[] = [];\n\n if (showForeverButton) {\n data.push(\n \n );\n }\n\n if (showDatePickers && !trailingTimeFrame) {\n data.push( );\n }\n\n if (showTrailingTimeSelector && trailingTimeFrame) {\n data.push(\n \n );\n }\n\n if (showDatePickers && showTrailingTimeSelector) {\n data.push(\n \n {trailingTimeFrame ? (\n \n ) : (\n \n )}\n \n );\n }\n\n useEffect(() => {\n if (!forever && showTrailingTimeSelector && trailingTimeFrame) {\n const newTrailingTimeFrame = calculateTrailingTimeFrame(startDate);\n const startDateFromTrailingTimeFrame = getStartDateFromValAndUnit(\n newTrailingTimeFrame.unit,\n newTrailingTimeFrame.value\n );\n\n if (startDateFromTrailingTimeFrame.getTime() !== startDate.getTime()) {\n setTrailingTimeFrame(newTrailingTimeFrame);\n setStartDate(startDateFromTrailingTimeFrame);\n }\n }\n }, [forever, setStartDate, setTrailingTimeFrame, showTrailingTimeSelector, startDate, trailingTimeFrame]);\n\n return data.reduce((acc, item) => {\n return (\n <>\n {acc} {item}\n >\n );\n });\n};\n","import { useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport {\n Checkbox,\n Divider,\n FormControl,\n ListItemText,\n ListSubheader,\n MenuItem,\n OutlinedInput,\n Select,\n SelectChangeEvent,\n Typography\n} from '@mui/material';\n\nimport { stampCardsSelector } from 'store/selectors';\nimport { store } from 'store/store';\nimport { introStepHomeSettings } from 'views/Home/JoyrideIntroduction/Settings/Home.JoyrideWidgetMenu.Steps';\nimport { IStampCardCampaign } from 'views/StampCardPage/stampCardPage.interfaces';\n\nimport { addCancelGridDragabilityToPopups } from '../../../../Grid/ResponsiveGridLayout.utils';\n\nexport const StampCardCampaignSelector: React.FC<{\n stampCardCampaignIds: string[];\n setStampCardCampaignIds: (stampCardCampaignIds: string[]) => void;\n}> = ({ setStampCardCampaignIds, stampCardCampaignIds }) => {\n const stampCardCampaigns = stampCardsSelector({ state: store.getState() });\n const [selected, setSelected] = useState(\n stampCardCampaigns.filter((el) => stampCardCampaignIds.includes(el.id)).map((el) => el.id)\n );\n\n const isAllSelected = stampCardCampaigns.length > 0 && selected.length === stampCardCampaigns.length;\n\n const handleChange = (event: SelectChangeEvent) => {\n const {\n target: { value }\n } = event;\n\n if (value.includes('all')) {\n if (isAllSelected) setSelected([]);\n else setSelected(stampCardCampaigns.map((el) => el.id));\n return;\n }\n\n setSelected(\n // On autofill we get a stringified value.\n typeof value === 'string' ? value.split(',') : value\n );\n };\n\n const onClose = () => {\n const selectedCampaigns = stampCardCampaigns.filter((el) => selected.includes(el.id));\n\n setStampCardCampaignIds(selectedCampaigns.map((el) => el.id));\n };\n\n const menuItem = (camp: IStampCardCampaign) => {\n return (\n \n -1} />\n \n \n );\n };\n\n const menuItems = (): React.ReactNode[] => {\n if (stampCardCampaigns.length === 1) {\n return [menuItem(stampCardCampaigns[0])];\n }\n\n const expired = stampCardCampaigns.filter(\n (el) => el.endDate && new Date(el.endDate).getTime() > new Date().getTime()\n );\n\n const active = stampCardCampaigns.filter(\n (el) => el.activated && !el.disabled && !expired.find((ex) => ex.id === el.id)\n );\n\n const testMode = stampCardCampaigns.filter(\n (el) => !el.activated && !el.disabled && !expired.find((ex) => ex.id === el.id)\n );\n\n const disabled = stampCardCampaigns.filter(\n (el) =>\n !active.find((ac) => ac.id === el.id) &&\n !testMode.find((tm) => tm.id === el.id) &&\n !expired.find((ex) => ex.id === el.id)\n );\n\n const items: React.ReactElement[] = [];\n\n if (active.length) {\n items.push(\n \n \n \n \n \n );\n items.push(...active.map(menuItem));\n }\n\n if (testMode.length) {\n items.push(\n \n \n \n \n \n );\n items.push(...testMode.map(menuItem));\n }\n\n if (expired.length) {\n items.push(\n \n \n \n \n \n );\n items.push(...expired.map(menuItem));\n }\n\n if (disabled.length) {\n items.push(\n \n \n \n \n \n );\n items.push(...disabled.map(menuItem));\n }\n\n return items;\n };\n\n return (\n \n }\n renderValue={(sel) => {\n if (sel.length === 0) {\n return ;\n }\n\n return sel.map((el) => stampCardCampaigns.find((c) => c.id === el)?.name)?.join(', ');\n }}\n MenuProps={{\n PaperProps: {\n style: { padding: '0px', margin: '0px' }\n }\n }}\n onOpen={() => addCancelGridDragabilityToPopups()}>\n {stampCardCampaigns.length > 1 && (\n \n 0 && selected.length < stampCardCampaigns.length}\n />\n \n \n )}\n {stampCardCampaigns.length > 1 && }\n {menuItems()}\n \n \n );\n};\n","import { useState } from 'react';\nimport { Translate } from 'react-redux-i18n';\n\nimport {\n Checkbox,\n Divider,\n FormControl,\n ListItemText,\n MenuItem,\n OutlinedInput,\n Select,\n SelectChangeEvent\n} from '@mui/material';\n\nimport { VenuesInterface } from 'store/features/merchantAndVenues/handlers';\nimport { venuesSelector } from 'store/selectors';\nimport { store } from 'store/store';\nimport { introStepHomeSettings } from 'views/Home/JoyrideIntroduction/Settings/Home.JoyrideWidgetMenu.Steps';\n\nimport { addCancelGridDragabilityToPopups } from '../../../../Grid/ResponsiveGridLayout.utils';\n\nexport const VenueSelector: React.FC<{\n venueIds: string[];\n setVenueIds: (venueIds: string[]) => void;\n}> = ({ setVenueIds, venueIds }) => {\n const venues = venuesSelector(store.getState());\n const [selected, setSelected] = useState(\n venues.filter((el) => venueIds.includes(el.id)).map((el) => el.venueTitle)\n );\n\n const isAllSelected = venues.length > 0 && selected.length === venues.length;\n\n const handleChange = (event: SelectChangeEvent) => {\n const {\n target: { value }\n } = event;\n\n if (value.includes('all')) {\n if (isAllSelected) setSelected([]);\n else setSelected(venues.map((el) => el.venueTitle));\n return;\n }\n\n setSelected(\n // On autofill we get a stringified value.\n typeof value === 'string' ? value.split(',') : value\n );\n };\n\n const onClose = () => {\n const selectedVenues = venues.filter((el) => selected.includes(el.venueTitle));\n\n setVenueIds(selectedVenues.map((el) => el.id));\n };\n\n const menuItem = (ven: VenuesInterface) => {\n return (\n \n -1 || venues.length === 1} />\n \n \n );\n };\n\n return (\n \n }\n renderValue={(sel) => {\n if (sel.length === 0) {\n return ;\n }\n return sel.join(', ');\n }}\n MenuProps={{\n PaperProps: {\n style: { padding: '0px', margin: '0px' }\n }\n }}\n onOpen={() => addCancelGridDragabilityToPopups()}>\n {venues.length > 1 && (\n \n 0 && selected.length < venues.length}\n />\n \n \n )}\n {venues.length > 1 && }\n {venues.map(menuItem)}\n \n \n );\n};\n","import React from 'react';\nimport { I18n } from 'react-redux-i18n';\n\nimport { Box, Checkbox, Divider, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';\n\nimport { VerticalFlexBox } from 'generalComponents/BoxModifications';\nimport LocaleAPI from 'store/locale';\nimport { stampCardSubscriptionSelector, venuesSelector } from 'store/selectors';\nimport { store } from 'store/store';\nimport { introStepHomeSettings } from 'views/Home/JoyrideIntroduction/Settings/Home.JoyrideWidgetMenu.Steps';\nimport { INTRO_WIDGET_MENU_CLASS_NAMES } from 'views/Home/JoyrideIntroduction/Settings/Home.JoyrideWidgetMenu.context';\n\nimport { SettingsMenuProps } from '../../../Contexts/ChartSettingsContext/ChartSettingsContext.interfaces';\nimport {\n AllowedMongoFilterSettings,\n ChartPresetPerCountry\n} from '../Interfaces/MongoDBWidgets.interfaces.chartSpecification';\nimport { ApplySettingsButton } from './SettingsMenu.ApplySettingsButton';\nimport { FilterRow } from './SettingsMenu.FilterRow';\nimport { ChartSettingsFilterTitle } from './SettingsMenu.MenuTitles';\nimport { NoFilterSettings } from './SettingsMenu.NoFilterSettings';\nimport { SelectTypeOfDateRangePicker } from './Utils/DateSelector/DateSelector';\nimport { StampCardCampaignSelector } from './Utils/StampCardCampaignSelector';\nimport { VenueSelector } from './Utils/VenueSelector';\n\nconst noFiltersAvailable = (\n allowedFilters: AllowedMongoFilterSettings[],\n multiVenue: boolean,\n subscribedToStampCardFeature: boolean\n) => {\n let filters = [...allowedFilters];\n if (!multiVenue) filters = filters.filter((el) => el !== 'venueId');\n if (!subscribedToStampCardFeature) filters = filters.filter((el) => el !== 'stampCardCampaignId');\n\n if (filters.length === 0) return true;\n};\n\nexport const SettingsMenu: React.FC = ({\n chartSpecification,\n startDate,\n setStartDate,\n endDate,\n setEndDate,\n hourlyPeriodic,\n setHourlyPeriodic,\n dailyPeriodic,\n setDailyPeriodic,\n forever,\n setForever,\n venueIdsStr,\n setVenueIdsStr,\n stampCardCampaignIdsStr,\n setStampCardCampaignIdsStr,\n trailingTimeFrameSetting,\n setTrailingTimeFrameSetting,\n closeMenu,\n localSettingsMenu\n}) => {\n const allowedFilters = chartSpecification.filters;\n const multiVenue = venuesSelector(store.getState()).length > 1;\n const subscribedToStampCardFeature = stampCardSubscriptionSelector(store.getState()).length > 0;\n\n const locale = LocaleAPI();\n\n const datePickers = () => {\n if (!allowedFilters.includes('createdAt') && !allowedFilters.includes('trailingTimeframe')) return null;\n\n return (\n \n \n \n \n \n \n );\n };\n\n const venueSelector = () => {\n if (!allowedFilters.includes('venueId') || !multiVenue) return null;\n\n return (\n \n \n \n );\n };\n\n const stampCardCampaignSelector = () => {\n if (!allowedFilters.includes('stampCardCampaignId') || !subscribedToStampCardFeature) return null;\n\n return (\n \n \n \n );\n };\n\n const hourlyPeriodicCheckbox = () => {\n if (!allowedFilters.includes('hourlyPeriodic')) return null;\n\n return (\n \n {\n setHourlyPeriodic(!hourlyPeriodic);\n }}\n disabled={dailyPeriodic}>\n \n \n \n \n \n \n );\n };\n\n const dailyPeriodicCheckbox = () => {\n if (!allowedFilters.includes('dailyPeriodic')) return null;\n\n return (\n \n {\n setDailyPeriodic(!dailyPeriodic);\n }}\n disabled={hourlyPeriodic}>\n \n \n \n \n \n \n );\n };\n\n const periodicSelectors = () => {\n if (allowedFilters.includes('dailyPeriodic') || allowedFilters.includes('hourlyPeriodic')) {\n return (\n \n \n {hourlyPeriodicCheckbox()}\n {dailyPeriodicCheckbox()}\n \n );\n }\n };\n\n if (noFiltersAvailable(allowedFilters, multiVenue, subscribedToStampCardFeature)) {\n return ;\n }\n\n return (\n \n {localSettingsMenu && (\n \n )}\n {datePickers()}\n {venueSelector()}\n {stampCardCampaignSelector()}\n {periodicSelectors()}\n {allowedFilters.length > 0 && (\n \n )}\n
\n );\n};\n","export enum Errors {\n Nothing = 'nothing',\n MaxLength = 'maxLength',\n StartAfterEnd = 'startAfterEnd'\n}\n\nexport const MAX_RANGE_IN_DAYS = Infinity;\n\nexport const TIME_FRAME_MINUTE_RANGE: number[] = [];\nfor (let x = 5; x <= 60; x += 5) TIME_FRAME_MINUTE_RANGE.push(x);\n\nexport const TIMEFRAME_HOUR_RANGE: number[] = [];\nfor (let x = 1; x <= 24; x += 1) TIMEFRAME_HOUR_RANGE.push(x);\n\nexport const TIMEFRAME_DAY_RANGE: number[] = [];\nfor (let x = 1; x <= 31; x += 1) TIMEFRAME_DAY_RANGE.push(x);\n\nexport const TIMEFRAME_MONTH_RANGE: number[] = [];\nfor (let x = 1; x <= 12; x += 1) TIMEFRAME_MONTH_RANGE.push(x);\n\nexport const FOREVER_START_YEAR = 0;\n","import { intervalToDuration, subDays, subHours, subMinutes, subMonths } from 'date-fns';\n\nimport {\n FOREVER_START_YEAR,\n TIMEFRAME_DAY_RANGE,\n TIMEFRAME_HOUR_RANGE,\n TIMEFRAME_MONTH_RANGE,\n TIME_FRAME_MINUTE_RANGE\n} from './DateSelector.constants';\nimport { TrailingTimeFrame, UnitValues } from './DateSelector.interfaces';\n\nexport const newDateZeroSecond = () => new Date(new Date().setSeconds(0, 0));\n\nexport const newForeverStartDate = () => new Date(new Date(`${FOREVER_START_YEAR}`).setSeconds(0, 0));\n\nexport const getStartDateFromValAndUnit = (unit: UnitValues, value: number) => {\n if (unit === 'minutes') return subMinutes(newDateZeroSecond(), value);\n if (unit === 'hours') return subHours(newDateZeroSecond(), value);\n if (unit === 'days') return subDays(newDateZeroSecond(), value);\n\n return subMonths(newDateZeroSecond(), value);\n};\n\nexport const calculateTrailingTimeFrame = (startDate: Date): TrailingTimeFrame => {\n const diff = intervalToDuration({ start: startDate, end: newDateZeroSecond() });\n\n if (diff.months) {\n if (diff.months >= TIMEFRAME_MONTH_RANGE.length) return { unit: 'months', value: TIMEFRAME_MONTH_RANGE.length };\n return { unit: 'months', value: diff.months };\n }\n\n if (diff.days) return { unit: 'days', value: diff.days };\n if (diff.hours) return { unit: 'hours', value: diff.hours };\n if (diff.minutes) return { unit: 'minutes', value: diff.minutes };\n\n return { unit: 'days', value: 30 };\n};\n\nexport const getTimeFrameRange = (unit: UnitValues): number[] => {\n if (unit === 'minutes') return TIME_FRAME_MINUTE_RANGE;\n if (unit === 'hours') return TIMEFRAME_HOUR_RANGE;\n if (unit === 'days') return TIMEFRAME_DAY_RANGE;\n return TIMEFRAME_MONTH_RANGE;\n};\n","export const unitRange = ['months', 'days', 'hours', 'minutes'] as const;\nexport type UnitValues = (typeof unitRange)[number];\n\nexport type TrailingTimeFrame = { unit: UnitValues; value: number };\n\nexport const DEFAULT_TRAILING_TIME_FRAME: TrailingTimeFrame = { unit: 'days', value: 30 };\n\nexport interface DateRangeProps {\n startDate: Date;\n setStartDate: (date: Date) => void;\n endDate: Date;\n setEndDate: (date: Date) => void;\n disabled?: boolean;\n maxRangeInDays?: number;\n}\n\nexport interface TrailingDateRangeProps {\n trailingTimeFrame: TrailingTimeFrame;\n setTrailingTimeFrame: (val: TrailingTimeFrame | undefined) => void;\n setStartDate: (date: Date) => void;\n setEndDate: (date: Date) => void;\n disabled?: boolean;\n}\n\nexport interface ForeverButtonProps {\n checked: boolean;\n setChecked: (val: boolean) => void;\n startDate: Date;\n setStartDate: (date: Date) => void;\n endDate: Date;\n setEndDate: (date: Date) => void;\n setTrailingTimeFrame: (val: TrailingTimeFrame | undefined) => void;\n}\n\nexport type SelectTypeOfDateRangePickerProps = DateRangeProps & {\n trailingTimeFrame: TrailingTimeFrame | undefined;\n setTrailingTimeFrame: (val: TrailingTimeFrame | undefined) => void;\n forever: boolean;\n setForever: React.Dispatch>;\n showForeverButton: boolean;\n showTrailingTimeSelector?: boolean;\n showDatePickers?: boolean;\n};\n\nexport enum Errors {\n Nothing = 'nothing',\n MaxLength = 'maxLength',\n StartAfterEnd = 'startAfterEnd'\n}\n","import { DashboardItemLayout } from 'views/Dashboard/Interfaces/Dashboard.layout';\n\nimport { GenerateLayout } from '../../helpers';\nimport { newForeverStartDate } from '../ChartFilters/Utils/DateSelector/DateSelector.helpers';\nimport { AllChartFilters } from '../Interfaces/MongoDBWidgets.interfaces.filters';\nimport { ChartNames } from '../Interfaces/MongoDBWidgets.interfaces.names';\n\nconst NumericTotalSize = { w: 3, h: 4, minW: 3, minH: 3 };\nconst TimeGraphSize = { w: 10, h: 8, minW: 6, minH: 4 };\n\nexport const ChartDefaultLayouts: { [key in keyof typeof ChartNames]: DashboardItemLayout } = {\n numberOfStampsPerStampCardCampaignHistogram: GenerateLayout({\n layoutType: ChartNames.numberOfStampsPerStampCardCampaignHistogram,\n w: 6,\n h: 9,\n minW: 4,\n minH: 9\n }),\n averageStampsPerStampCardDonut: GenerateLayout({\n layoutType: ChartNames.averageStampsPerStampCardDonut,\n w: 6,\n h: 7,\n minW: 3,\n minH: 5\n }),\n SMSCampaignsTotal: GenerateLayout({ layoutType: ChartNames.SMSCampaignsTotal, ...NumericTotalSize }),\n campaignSMSSentTotal: GenerateLayout({ layoutType: ChartNames.campaignSMSSentTotal, ...NumericTotalSize }),\n numberOfStampsTotal: GenerateLayout({ layoutType: ChartNames.numberOfStampsTotal, ...NumericTotalSize }),\n stampCardsRewardsClaimedTotal: GenerateLayout({\n layoutType: ChartNames.stampCardsRewardsClaimedTotal,\n ...NumericTotalSize\n }),\n stampCardsCreatedTotal: GenerateLayout({ layoutType: ChartNames.stampCardsCreatedTotal, ...NumericTotalSize }),\n numberOfUniqueRegistrationsTotal: GenerateLayout({\n layoutType: ChartNames.numberOfUniqueRegistrationsTotal,\n filter: { forever: true, createdAt: { $gte: newForeverStartDate() } } as AllChartFilters, // Since we check each field in the chartContext, we can leave out required parameters here\n ...NumericTotalSize\n }),\n numberOfRegistrationsTotal: GenerateLayout({\n layoutType: ChartNames.numberOfRegistrationsTotal,\n ...NumericTotalSize\n }),\n registrationsOverTime: GenerateLayout({\n layoutType: ChartNames.registrationsOverTime,\n ...TimeGraphSize\n }),\n registrationsOverTimePerVenue: GenerateLayout({\n layoutType: ChartNames.registrationsOverTimePerVenue,\n ...TimeGraphSize\n }),\n registrationsNewOrReturningOverTime: GenerateLayout({\n layoutType: ChartNames.registrationsNewOrReturningOverTime,\n ...TimeGraphSize\n }),\n purchasesOverTime: GenerateLayout({ layoutType: ChartNames.purchasesOverTime, ...TimeGraphSize }),\n newStampCardsOverTime: GenerateLayout({ layoutType: ChartNames.newStampCardsOverTime, ...TimeGraphSize }),\n numberOfStampsOverTime: GenerateLayout({ layoutType: ChartNames.numberOfStampsOverTime, ...TimeGraphSize }),\n numberOfStampsOverTimePerVenue: GenerateLayout({\n layoutType: ChartNames.numberOfStampsOverTimePerVenue,\n ...TimeGraphSize\n }),\n numberOfStampsNewOrReturningOverTime: GenerateLayout({\n layoutType: ChartNames.numberOfStampsNewOrReturningOverTime,\n ...TimeGraphSize\n })\n};\n","import { RenderingSpec } from '@mongodb-js/charts-embed-dom';\nimport sassVariables from 'assets/scss/export.module.scss';\n\nimport { CHART_VERSION, MongoDBRenderingSpecs } from '../Interfaces/MongoDBWidgets.interfaces.chartSpecification';\n\nconst ColorPalette = [\n sassVariables.payAttDarkBlue,\n sassVariables.payAttGreen,\n sassVariables.payAttPurple,\n 'red',\n sassVariables.payAttPink,\n sassVariables.payAttDarkTurquoise,\n sassVariables.payAttGray,\n sassVariables.payAttBlue,\n sassVariables.payAttBlack\n];\n\nexport const DefaultRenderSpecDescreteLine: RenderingSpec = {\n version: CHART_VERSION,\n options: {\n colorPalette: { type: 'discrete', value: ColorPalette },\n markers: false,\n lineSmoothing: 'monotone'\n },\n axes: { x: { labelAngle: 'diagonal' } }\n};\n\nexport const DefaultRenderSpecBarChart: RenderingSpec = {\n version: CHART_VERSION,\n options: { colorPalette: { type: 'discrete', value: ColorPalette } }\n};\n\nexport const DefaultRenderSpecNumber: RenderingSpec = {\n version: CHART_VERSION\n};\n\nexport const DefaultRenderHistogram: RenderingSpec = {\n version: CHART_VERSION,\n options: { colorPalette: { type: 'continuous', color: 'purpleblue' } }\n};\n\nexport const DefaultRenderDonut: RenderingSpec = {\n version: CHART_VERSION,\n options: { colorPalette: { type: 'discrete', value: ColorPalette }, categoryLabels: true }\n};\n\nexport const mongoDBRenderingSpecs: MongoDBRenderingSpecs = {\n numberOfStampsPerStampCardCampaignHistogram: {\n se: {\n ...DefaultRenderHistogram,\n title: 'Antal stämplar per stämpelkortskampanj',\n channels: {\n color: { labelOverride: 'Antal stämplar' },\n x: { labelOverride: 'Stämpelkortskampanj' },\n y: { labelOverride: 'Antal stämplar' }\n }\n },\n en: {\n ...DefaultRenderHistogram,\n title: 'Number of stamps per stamp card campaign',\n channels: {\n color: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Stamp card campaign' },\n y: { labelOverride: 'Number of stamps' }\n }\n }\n },\n averageStampsPerStampCardDonut: {\n se: {\n ...DefaultRenderDonut,\n title: 'Distribution av stämplar på kunders stämpelkort',\n channels: { value: { labelOverride: 'Antal stämplar' }, label: { labelOverride: 'Grupp' } }\n },\n en: {\n ...DefaultRenderDonut,\n title: 'Distribution of stamps on customers stamp cards',\n channels: { value: { labelOverride: 'Stamps' }, label: { labelOverride: 'Group' } }\n }\n },\n SMSCampaignsTotal: {\n se: {\n ...DefaultRenderSpecNumber,\n title: 'SMS-kampanjer',\n channels: { value: { labelOverride: 'SMS-kampanjer', numberThousandsSeparators: true } }\n },\n en: {\n ...DefaultRenderSpecNumber,\n title: 'SMS campaigns',\n channels: { value: { labelOverride: 'SMS campaigns', numberThousandsSeparators: true } }\n }\n },\n campaignSMSSentTotal: {\n se: {\n ...DefaultRenderSpecNumber,\n title: 'SMS skickade i SMS-kampanjer',\n channels: { value: { labelOverride: 'SMS skickade', numberThousandsSeparators: true } }\n },\n en: {\n ...DefaultRenderSpecNumber,\n title: 'SMS sent in SMS campaigns',\n channels: { value: { labelOverride: 'SMS sent', numberThousandsSeparators: true } }\n }\n },\n numberOfStampsTotal: {\n se: {\n ...DefaultRenderSpecNumber,\n title: 'Antal stämplar utdelade',\n channels: { value: { labelOverride: 'Stämplar', numberThousandsSeparators: true } }\n },\n en: {\n ...DefaultRenderSpecNumber,\n title: 'Number of stamps handed out',\n channels: { value: { labelOverride: 'Stamp count', numberThousandsSeparators: true } }\n }\n },\n stampCardsRewardsClaimedTotal: {\n se: {\n ...DefaultRenderSpecNumber,\n title: 'Stämpelkortsbelöningar uthämtade',\n channels: { value: { labelOverride: 'Antal belöningar', numberThousandsSeparators: true } }\n },\n en: {\n ...DefaultRenderSpecNumber,\n title: 'Stamp card rewards claimed',\n channels: { value: { labelOverride: 'Rewards', numberThousandsSeparators: true } }\n }\n },\n stampCardsCreatedTotal: {\n se: {\n ...DefaultRenderSpecNumber,\n title: 'Stämpelkort skapade åt kunder',\n channels: { value: { labelOverride: 'Stämpelkort', numberThousandsSeparators: true } }\n },\n en: {\n ...DefaultRenderSpecNumber,\n title: 'Stamp cards created for customers',\n channels: { value: { labelOverride: 'Stamp cards', numberThousandsSeparators: true } }\n }\n },\n numberOfUniqueRegistrationsTotal: {\n se: {\n ...DefaultRenderSpecNumber,\n title: 'Medlemmar',\n channels: { value: { labelOverride: 'Medlemmar', numberThousandsSeparators: true } }\n },\n en: {\n ...DefaultRenderSpecNumber,\n title: 'Members',\n channels: { value: { labelOverride: 'Members', numberThousandsSeparators: true } }\n }\n },\n numberOfRegistrationsTotal: {\n se: {\n ...DefaultRenderSpecNumber,\n title: 'Antal registreringar',\n channels: { value: { labelOverride: 'Registreringar', numberThousandsSeparators: true } }\n },\n en: {\n ...DefaultRenderSpecNumber,\n title: 'Number of registrations',\n channels: { value: { labelOverride: 'Registrations', numberThousandsSeparators: true } }\n }\n },\n registrationsOverTimeHourly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Timme' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Hour' }\n }\n }\n },\n registrationsOverTimeDaily: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Datum' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations ',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Date' }\n }\n }\n },\n registrationsOverTimeWeekly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Vecka' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Week' }\n }\n }\n },\n registrationsOverTimeMonthly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Månad' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Month' }\n }\n }\n },\n registrationsOverTimeYearly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'År' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Year' }\n }\n }\n },\n registrationsOverTimeWeekdayPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Registreringar per veckodag',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Veckodag' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Registrations per weekday',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Weekday' }\n }\n }\n },\n registrationsOverTimeHourPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Registreringar per timme',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Timme' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Registrations per hour',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Hour' }\n }\n }\n },\n registrationsOverTimePerVenueHourly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Timme' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Hour' }\n }\n }\n },\n registrationsOverTimePerVenueDaily: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Datum' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations ',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Date' }\n }\n }\n },\n registrationsOverTimePerVenueWeekly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Vecka' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Week' }\n }\n }\n },\n registrationsOverTimePerVenueMonthly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Månad' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Month' }\n }\n }\n },\n registrationsOverTimePerVenueYearly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'År' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Year' }\n }\n }\n },\n registrationsOverTimePerVenueWeekdayPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Registreringar per veckodag',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Veckodag' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Registrations per weekday',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Weekday' }\n }\n }\n },\n registrationsOverTimePerVenueHourPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Registreringar per timme',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Timme' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Registrations per hour',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Hour' }\n }\n }\n },\n registrationsNewOrReturningOverTimeHourly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Timme' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Hour' },\n color: { labelOverride: 'Customer' }\n }\n }\n },\n registrationsNewOrReturningOverTimeDaily: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Datum' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations ',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Date' },\n color: { labelOverride: 'Customer' }\n }\n }\n },\n registrationsNewOrReturningOverTimeWeekly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Vecka' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Week' },\n color: { labelOverride: 'Customer' }\n }\n }\n },\n registrationsNewOrReturningOverTimeMonthly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Månad' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Month' },\n color: { labelOverride: 'Customer' }\n }\n }\n },\n registrationsNewOrReturningOverTimeYearly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'År' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Year' },\n color: { labelOverride: 'Customer' }\n }\n }\n },\n registrationsNewOrReturningOverTimeWeekdayPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Registreringar per veckodag',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Veckodag' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Registrations per weekday',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Weekday' },\n color: { labelOverride: 'Customer' }\n }\n }\n },\n registrationsNewOrReturningOverTimeHourPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Registreringar per timme',\n channels: {\n y: { labelOverride: 'Antal Registreringar' },\n x: { labelOverride: 'Timme' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Registrations per hour',\n channels: {\n y: { labelOverride: 'Number of Registrations' },\n x: { labelOverride: 'Hour' },\n color: { labelOverride: 'Customer' }\n }\n }\n },\n purchasesOverTimeHourly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar kontra köp',\n channels: {\n y: { labelOverride: 'Antal' },\n x: { labelOverride: 'Timme' },\n color: { labelOverride: 'Serie' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations against purchases',\n channels: {\n y: { labelOverride: 'Value' },\n x: { labelOverride: 'Hour' },\n color: { labelOverride: 'Series' }\n }\n }\n },\n purchasesOverTimeDaily: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar kontra köp',\n channels: {\n y: { labelOverride: 'Antal' },\n x: { labelOverride: 'Datum' },\n color: { labelOverride: 'Serie' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations against purchases',\n channels: {\n y: { labelOverride: 'Value' },\n x: { labelOverride: 'Date' },\n color: { labelOverride: 'Series' }\n }\n }\n },\n purchasesOverTimeWeekly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar kontra köp',\n channels: {\n y: { labelOverride: 'Antal' },\n x: { labelOverride: 'Vecka' },\n color: { labelOverride: 'Serie' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations against purchases',\n channels: {\n y: { labelOverride: 'Value' },\n x: { labelOverride: 'Week' },\n color: { labelOverride: 'Series' }\n }\n }\n },\n purchasesOverTimeMonthly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar kontra köp',\n channels: {\n y: { labelOverride: 'Antal' },\n x: { labelOverride: 'Månad' },\n color: { labelOverride: 'Serie' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations against purchases',\n channels: {\n y: { labelOverride: 'Value' },\n x: { labelOverride: 'Month' },\n color: { labelOverride: 'Series' }\n }\n }\n },\n purchasesOverTimeYearly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registreringar kontra köp',\n channels: { y: { labelOverride: 'Antal' }, x: { labelOverride: 'År' }, color: { labelOverride: 'Serie' } }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Registrations against purchases',\n channels: {\n y: { labelOverride: 'Value' },\n x: { labelOverride: 'Year' },\n color: { labelOverride: 'Series' }\n }\n }\n },\n purchasesOverTimeWeekdayPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Registreringar kontra köp per veckodag',\n channels: {\n y: { labelOverride: 'Antal' },\n x: { labelOverride: 'Veckodag' },\n color: { labelOverride: 'Serie' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Registrations against purchases per weekday',\n channels: {\n y: { labelOverride: 'Value' },\n x: { labelOverride: 'Weekday' },\n color: { labelOverride: 'Series' }\n }\n }\n },\n purchasesOverTimeHourPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Registreringar kontra köp per timme',\n channels: {\n y: { labelOverride: 'Antal' },\n x: { labelOverride: 'Timme' },\n color: { labelOverride: 'Serie' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Registrations against purchases per hour',\n channels: {\n y: { labelOverride: 'Value' },\n x: { labelOverride: 'Hour' },\n color: { labelOverride: 'Series' }\n }\n }\n },\n newStampCardsOverTimeHourly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Nya stämpelkort utgivna',\n channels: {\n y: { labelOverride: 'Stämpelkort' },\n x: { labelOverride: 'Timme' },\n color: { labelOverride: 'Stämpelkortskampanj' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'New stamp cards created',\n channels: {\n y: { labelOverride: 'Stamp cards' },\n x: { labelOverride: 'Hour' },\n color: { labelOverride: 'Stamp card campaign' }\n }\n }\n },\n newStampCardsOverTimeDaily: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Nya stämpelkort utgivna',\n channels: {\n y: { labelOverride: 'Stämpelkort' },\n x: { labelOverride: 'Datum' },\n color: { labelOverride: 'Stämpelkortskampanj' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'New stamp cards created',\n channels: {\n y: { labelOverride: 'Stamp cards' },\n x: { labelOverride: 'Date' },\n color: { labelOverride: 'Stamp card campaign' }\n }\n }\n },\n newStampCardsOverTimeWeekly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Nya stämpelkort utgivna',\n channels: {\n y: { labelOverride: 'Stämpelkort' },\n x: { labelOverride: 'Vecka' },\n color: { labelOverride: 'Stämpelkortskampanj' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'New stamp cards created',\n channels: {\n y: { labelOverride: 'Stamp cards' },\n x: { labelOverride: 'Week' },\n color: { labelOverride: 'Stamp card campaign' }\n }\n }\n },\n newStampCardsOverTimeMonthly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Nya stämpelkort utgivna',\n channels: {\n y: { labelOverride: 'Stämpelkort' },\n x: { labelOverride: 'Månad' },\n color: { labelOverride: 'Stämpelkortskampanj' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'New stamp cards created',\n channels: {\n y: { labelOverride: 'Stamp cards' },\n x: { labelOverride: 'Month' },\n color: { labelOverride: 'Stamp card campaign' }\n }\n }\n },\n newStampCardsOverTimeYearly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Nya stämpelkort utgivna',\n channels: {\n y: { labelOverride: 'Stämpelkort' },\n x: { labelOverride: 'År' },\n color: { labelOverride: 'Stämpelkortskampanj' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'New stamp cards created',\n channels: {\n y: { labelOverride: 'Stamp cards' },\n x: { labelOverride: 'Year' },\n color: { labelOverride: 'Stamp card campaign' }\n }\n }\n },\n newStampCardsOverTimeWeekdayPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Nya stämpelkort utgivna per veckodag',\n channels: {\n y: { labelOverride: 'Stämpelkort' },\n x: { labelOverride: 'Veckodag' },\n color: { labelOverride: 'Stämpelkortskampanj' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'New stamp cards created per weekday',\n channels: {\n y: { labelOverride: 'Stamp cards' },\n x: { labelOverride: 'Weekday' },\n color: { labelOverride: 'Stamp card campaign' }\n }\n }\n },\n newStampCardsOverTimeHourPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Nya stämpelkort utgivna per timme',\n channels: {\n y: { labelOverride: 'Stämpelkort' },\n x: { labelOverride: 'Timme' },\n color: { labelOverride: 'Stämpelkortskampanj' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'New stamp cards created per hour',\n channels: {\n y: { labelOverride: 'Stamp cards' },\n x: { labelOverride: 'Hour' },\n color: { labelOverride: 'Stamp card campaign' }\n }\n }\n },\n numberOfStampsOverTimeHourly: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Timme' },\n color: { labelOverride: 'Typ av registrering' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Hour' },\n color: { labelOverride: 'Type of registration' }\n }\n }\n },\n numberOfStampsOverTimeDaily: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Datum' },\n color: { labelOverride: 'Typ av registrering' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Date' },\n color: { labelOverride: 'Type of registration' }\n }\n }\n },\n numberOfStampsOverTimeWeekly: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Vecka' },\n color: { labelOverride: 'Typ av registrering' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Week' },\n color: { labelOverride: 'Type of registration' }\n }\n }\n },\n numberOfStampsOverTimeMonthly: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Månad' },\n color: { labelOverride: 'Typ av registrering' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Month' },\n color: { labelOverride: 'Type of registration' }\n }\n }\n },\n numberOfStampsOverTimeYearly: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'år' },\n color: { labelOverride: 'Typ av registrering' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Year' },\n color: { labelOverride: 'Type of registration' }\n }\n }\n },\n numberOfStampsOverTimeWeekdayPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna per veckodag',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Veckodag' },\n color: { labelOverride: 'Typ av registrering' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out per weekday',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Weekday' },\n color: { labelOverride: 'Type of registration' }\n }\n }\n },\n numberOfStampsOverTimeHourPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna per timme',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Timme' },\n color: { labelOverride: 'Typ av registrering' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out per hour',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Hour' },\n color: { labelOverride: 'Type of registration' }\n }\n }\n },\n numberOfStampsOverTimePerVenueHourly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Timme' },\n color: { labelOverride: 'Verksamhet' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Hour' },\n color: { labelOverride: 'Venue' }\n }\n }\n },\n numberOfStampsOverTimePerVenueDaily: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Datum' },\n color: { labelOverride: 'Verksamhet' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Date' },\n color: { labelOverride: 'Venue' }\n }\n }\n },\n numberOfStampsOverTimePerVenueWeekly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Vecka' },\n color: { labelOverride: 'Verksamhet' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Week' },\n color: { labelOverride: 'Venue' }\n }\n }\n },\n numberOfStampsOverTimePerVenueMonthly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Månad' },\n color: { labelOverride: 'Verksamhet' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Month' },\n color: { labelOverride: 'Venue' }\n }\n }\n },\n numberOfStampsOverTimePerVenueYearly: {\n se: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'år' },\n color: { labelOverride: 'Verksamhet' }\n }\n },\n en: {\n ...DefaultRenderSpecDescreteLine,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Year' },\n color: { labelOverride: 'Venue' }\n }\n }\n },\n numberOfStampsOverTimePerVenueWeekdayPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna per veckodag',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Veckodag' },\n color: { labelOverride: 'Verksamhet' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out per weekday',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Weekday' },\n color: { labelOverride: 'Venue' }\n }\n }\n },\n numberOfStampsOverTimePerVenueHourPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna per timme',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Timme' },\n color: { labelOverride: 'Verksamhet' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out per hour',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Hour' },\n color: { labelOverride: 'Venue' }\n }\n }\n },\n numberOfStampsNewOrReturningOverTimeHourly: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Timme' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Hour' },\n color: { labelOverride: 'Customer' }\n }\n }\n },\n numberOfStampsNewOrReturningOverTimeDaily: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Datum' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Date' },\n color: { labelOverride: 'Customer' }\n }\n }\n },\n numberOfStampsNewOrReturningOverTimeWeekly: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Vecka' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Week' },\n color: { labelOverride: 'Customer' }\n }\n }\n },\n numberOfStampsNewOrReturningOverTimeMonthly: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Månad' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Month' },\n color: { labelOverride: 'Customer' }\n }\n }\n },\n numberOfStampsNewOrReturningOverTimeYearly: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'År' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Year' },\n color: { labelOverride: 'Customer' }\n }\n }\n },\n numberOfStampsNewOrReturningOverTimeWeekdayPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna per veckodag',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Veckodag' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out per weekday',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Weekday' },\n color: { labelOverride: 'Customer' }\n }\n }\n },\n numberOfStampsNewOrReturningOverTimeHourPeriodic: {\n se: {\n ...DefaultRenderSpecBarChart,\n title: 'Antal stämplar utgivna per timme',\n channels: {\n y: { labelOverride: 'Antal stämplar utgivna' },\n x: { labelOverride: 'Timme' },\n color: { labelOverride: 'Kund' }\n }\n },\n en: {\n ...DefaultRenderSpecBarChart,\n title: 'Number of stamps handed out per hour',\n channels: {\n y: { labelOverride: 'Number of stamps' },\n x: { labelOverride: 'Hour' },\n color: { labelOverride: 'Customer' }\n }\n }\n }\n};\n","import { AllowedMongoFilterSettings } from '../Interfaces/MongoDBWidgets.interfaces.chartSpecification';\nimport { ChartNames } from '../Interfaces/MongoDBWidgets.interfaces.names';\n\nexport const AllowedChartFilters: { [key in keyof typeof ChartNames]: AllowedMongoFilterSettings[] } = {\n numberOfStampsPerStampCardCampaignHistogram: [],\n averageStampsPerStampCardDonut: ['stampCardCampaignId'],\n SMSCampaignsTotal: ['venueId'],\n campaignSMSSentTotal: ['venueId'],\n numberOfStampsTotal: ['stampCardCampaignId', 'createdAt', 'trailingTimeframe', 'forever'],\n stampCardsRewardsClaimedTotal: ['stampCardCampaignId', 'createdAt', 'trailingTimeframe', 'forever'],\n stampCardsCreatedTotal: ['stampCardCampaignId', 'createdAt', 'trailingTimeframe', 'forever'],\n numberOfUniqueRegistrationsTotal: ['venueId', 'createdAt', 'trailingTimeframe', 'forever'],\n numberOfRegistrationsTotal: ['venueId', 'createdAt', 'trailingTimeframe', 'forever'],\n registrationsOverTime: ['venueId', 'createdAt', 'hourlyPeriodic', 'dailyPeriodic', 'trailingTimeframe', 'forever'],\n registrationsOverTimePerVenue: [\n 'venueId',\n 'createdAt',\n 'hourlyPeriodic',\n 'dailyPeriodic',\n 'trailingTimeframe',\n 'forever'\n ],\n registrationsNewOrReturningOverTime: [\n 'venueId',\n 'createdAt',\n 'hourlyPeriodic',\n 'dailyPeriodic',\n 'trailingTimeframe',\n 'forever'\n ],\n purchasesOverTime: ['venueId', 'createdAt', 'hourlyPeriodic', 'dailyPeriodic', 'trailingTimeframe', 'forever'],\n newStampCardsOverTime: [\n 'stampCardCampaignId',\n 'createdAt',\n 'hourlyPeriodic',\n 'dailyPeriodic',\n 'trailingTimeframe',\n 'forever'\n ],\n numberOfStampsOverTime: [\n 'stampCardCampaignId',\n 'createdAt',\n 'hourlyPeriodic',\n 'dailyPeriodic',\n 'trailingTimeframe',\n 'forever'\n ],\n numberOfStampsOverTimePerVenue: [\n 'stampCardCampaignId',\n 'createdAt',\n 'hourlyPeriodic',\n 'dailyPeriodic',\n 'trailingTimeframe',\n 'forever'\n ],\n numberOfStampsNewOrReturningOverTime: [\n 'createdAt',\n 'trailingTimeframe',\n 'forever',\n 'hourlyPeriodic',\n 'dailyPeriodic',\n 'stampCardCampaignId'\n ]\n};\n","import { ChartTypes } from '../Interfaces/MongoDBWidgets.interfaces.chartSpecification';\nimport { AllChartsStructure } from '../Interfaces/MongoDBWidgets.interfaces.structure';\nimport { ChartDefaultLayouts } from './ChartSpecification.DefaultLayouts';\nimport { AllowedChartFilters } from './ChartSpecification.Filters';\nimport { mongoDBRenderingSpecs } from './ChartSpecification.MongoDBRenderingSpecs';\n\nexport const ChartSpecification: AllChartsStructure = {\n registrationsOverTime: {\n type: ChartTypes.LINE,\n filters: AllowedChartFilters.registrationsOverTime,\n defaultLayout: ChartDefaultLayouts.registrationsOverTime,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimeDaily,\n dev: '9198594a-4330-4378-bbac-2c8a79f629e7',\n staging: '47afeffa-28de-40c2-ab25-fd27375b46ed',\n prod: 'ccda2643-d952-4fed-b305-ccdfd8af1922'\n },\n hourly: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimeHourly,\n dev: '03ef818c-0d58-413c-85c9-3708841da710',\n staging: 'a6e89c97-f00d-420f-af6a-ecd3b28adaf4',\n prod: '6765b1e3-f8c4-4347-9a69-fa2a6eb50c3d'\n },\n daily: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimeDaily,\n dev: '9198594a-4330-4378-bbac-2c8a79f629e7',\n staging: '47afeffa-28de-40c2-ab25-fd27375b46ed',\n prod: 'ccda2643-d952-4fed-b305-ccdfd8af1922'\n },\n weekly: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimeWeekly,\n dev: 'f67ae4c2-87ca-48e4-99c9-916749947893',\n staging: '2b08a05a-5826-439c-a1a4-8fd38a188823',\n prod: '1b61137b-7ea8-4ecf-90b1-71e696409911'\n },\n monthly: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimeMonthly,\n dev: '30a8ebf4-1dc4-4d5d-beb8-6815864ad51d',\n staging: 'b4034973-fca5-45da-aa75-8157a550cee0',\n prod: '0e1194f2-8172-4ec0-b5b0-610b0e299f56'\n },\n yearly: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimeYearly,\n dev: '65cd31ce-efeb-476f-8493-7212fbe15940',\n staging: '5b56d1d3-d28c-4ae9-adb3-05d51abad5c7',\n prod: 'e136fe5d-fef5-4adc-a31b-452bb31b3342'\n },\n dailyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimeWeekdayPeriodic,\n dev: 'e78f68d4-dcb5-4fd8-add9-e304ec3a753b',\n staging: 'd1836dfe-3e3b-4981-b850-0ebb5161cea0',\n prod: '710c413c-fae4-467e-b6df-33b95fe7271f'\n },\n hourlyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimeHourPeriodic,\n dev: '7063e2cb-8cd9-4f00-b122-945fcf24347e',\n staging: 'd42e405b-a63c-4ccb-ab72-bd4efc92c922',\n prod: 'a48a9129-e484-4217-aeac-f77930d6dd14'\n }\n }\n },\n registrationsOverTimePerVenue: {\n type: ChartTypes.LINE,\n filters: AllowedChartFilters.registrationsOverTimePerVenue,\n defaultLayout: ChartDefaultLayouts.registrationsOverTimePerVenue,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimePerVenueDaily,\n dev: '6633624a-59f2-49f5-8247-ed8f07c13f24',\n staging: '6633a370-49d2-4c15-8e75-d1f6146a9734',\n prod: '662166a3-3b26-42c6-8573-ed6084290c95'\n },\n hourly: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimePerVenueHourly,\n dev: '6633624a-59f2-4420-8a66-ed8f07c13f26',\n staging: '6633a370-49d2-4a9f-89e9-d1f6146a9736',\n prod: '59674d5c-cf35-4493-bf34-f22e872f039c'\n },\n daily: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimePerVenueDaily,\n dev: '6633624a-59f2-49f5-8247-ed8f07c13f24',\n staging: '6633a370-49d2-4c15-8e75-d1f6146a9734',\n prod: '662166a3-3b26-42c6-8573-ed6084290c95'\n },\n weekly: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimePerVenueWeekly,\n dev: '6633624a-59f2-4fb9-82ff-ed8f07c13f2a',\n staging: '6633a370-49d2-4894-8f6f-d1f6146a973a',\n prod: 'c610d1e6-a34b-492e-9d54-7c1add0435b6'\n },\n monthly: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimePerVenueMonthly,\n dev: '6633624a-59f2-43c6-8da2-ed8f07c13f28',\n staging: '6633a370-49d2-4fc7-8244-d1f6146a9738',\n prod: 'f72fff27-ef23-42ff-9d1b-6100d9b12dda'\n },\n yearly: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimePerVenueYearly,\n dev: '6633624a-59f2-499c-81b9-ed8f07c13f2c',\n staging: '6633a370-49d2-461a-819b-d1f6146a973c',\n prod: 'b2ea3170-e95e-41ea-a12b-20fbfc561fa4'\n },\n dailyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.registrationsOverTimePerVenueWeekdayPeriodic,\n dev: '6633624a-59f2-46b8-8615-ed8f07c13f2e',\n staging: '6633a370-49d2-4499-8afc-d1f6146a973e',\n prod: 'c1671299-18fc-4b4b-876c-d7db3673eacb'\n },\n hourlyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.registrationsNewOrReturningOverTimeHourPeriodic,\n dev: '6633624a-59f2-44e0-83bf-ed8f07c13f30',\n staging: '6633a370-49d2-40a7-850d-d1f6146a9740',\n prod: 'ebeb9189-7530-45c4-a635-a4c7d016fa6a'\n }\n }\n },\n registrationsNewOrReturningOverTime: {\n type: ChartTypes.LINE,\n filters: AllowedChartFilters.registrationsNewOrReturningOverTime,\n defaultLayout: ChartDefaultLayouts.registrationsNewOrReturningOverTime,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.registrationsNewOrReturningOverTimeDaily,\n dev: '64831249-1717-4ca1-812a-8659c8d19784',\n staging: '6578ab17-36db-423a-88dd-d4da6652236c',\n prod: '6578aa90-6a42-4c49-8af3-12a8d7f51d65'\n },\n hourly: {\n renderingSpec: mongoDBRenderingSpecs.registrationsNewOrReturningOverTimeHourly,\n dev: 'cb77230a-d77e-4429-a6be-d34664b2e542',\n staging: '6578ab17-36db-4ebd-86be-d4da6652237e',\n prod: '6578aa90-6a42-4cad-838f-12a8d7f51d78'\n },\n daily: {\n renderingSpec: mongoDBRenderingSpecs.registrationsNewOrReturningOverTimeDaily,\n dev: '64831249-1717-4ca1-812a-8659c8d19784',\n staging: '6578ab17-36db-423a-88dd-d4da6652236c',\n prod: '6578aa90-6a42-4c49-8af3-12a8d7f51d65'\n },\n weekly: {\n renderingSpec: mongoDBRenderingSpecs.registrationsNewOrReturningOverTimeWeekly,\n dev: '3ace09c8-dc00-4af9-9b01-aff11e63e55e',\n staging: '6578ab17-36db-4673-81a9-d4da66522378',\n prod: '6578aa90-6a42-4409-83cc-12a8d7f51d72'\n },\n monthly: {\n renderingSpec: mongoDBRenderingSpecs.registrationsNewOrReturningOverTimeMonthly,\n dev: 'ea4aaf62-9988-4090-bfb2-1f266b48dbdf',\n staging: '6578ab17-36db-4af5-8fae-d4da6652237a',\n prod: '6578aa90-6a42-4e1c-8afd-12a8d7f51d74'\n },\n yearly: {\n renderingSpec: mongoDBRenderingSpecs.registrationsNewOrReturningOverTimeYearly,\n dev: '5e151ae9-4733-4d62-986c-d100da43b852',\n staging: '6578ab17-36db-4054-8782-d4da6652237c',\n prod: '6578aa90-6a42-4324-8291-12a8d7f51d76'\n },\n dailyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.registrationsNewOrReturningOverTimeWeekdayPeriodic,\n dev: '8ffdaff7-8b62-459e-adb9-8bd3eafe3e8a',\n staging: '6578ab17-36db-4481-8a77-d4da665223a6',\n prod: '6578aa90-6a42-4b2c-840c-12a8d7f51da0'\n },\n hourlyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.registrationsNewOrReturningOverTimeHourPeriodic,\n dev: '7c89f778-8b8e-4c75-afee-b97010131be1',\n staging: '6578ab17-36db-4f77-85d7-d4da665223a8',\n prod: '6578aa90-6a42-45a7-8ef6-12a8d7f51da2'\n }\n }\n },\n purchasesOverTime: {\n type: ChartTypes.LINE,\n filters: AllowedChartFilters.purchasesOverTime,\n defaultLayout: ChartDefaultLayouts.purchasesOverTime,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.purchasesOverTimeDaily,\n dev: 'ae3d8697-1f8e-41fa-9c32-c28dee9e6b8d',\n staging: '6578ab17-36db-406c-88d5-d4da66522384',\n prod: '6578aa90-6a42-46d1-8c34-12a8d7f51d7e'\n },\n hourly: {\n renderingSpec: mongoDBRenderingSpecs.purchasesOverTimeHourly,\n dev: 'a09a0652-bd79-4315-8fc2-3b0880ecc399',\n staging: '6578ab17-36db-4b67-8334-d4da66522380',\n prod: '6578aa90-6a42-4ca6-8eb9-12a8d7f51d7a'\n },\n daily: {\n renderingSpec: mongoDBRenderingSpecs.purchasesOverTimeDaily,\n dev: 'ae3d8697-1f8e-41fa-9c32-c28dee9e6b8d',\n staging: '6578ab17-36db-406c-88d5-d4da66522384',\n prod: '6578aa90-6a42-46d1-8c34-12a8d7f51d7e'\n },\n weekly: {\n renderingSpec: mongoDBRenderingSpecs.purchasesOverTimeWeekly,\n dev: 'ca37b853-f574-4c0c-88c0-79434aa49fc6',\n staging: '6578ab17-36db-4c1a-8a5d-d4da66522382',\n prod: '6578aa90-6a42-46ac-84d1-12a8d7f51d7c'\n },\n monthly: {\n renderingSpec: mongoDBRenderingSpecs.purchasesOverTimeMonthly,\n dev: 'dda18148-7db2-428e-970d-26a2d89b3c18',\n staging: '6578ab17-36db-4797-8b09-d4da6652236e',\n prod: '6578aa90-6a42-45b3-850d-12a8d7f51d68'\n },\n yearly: {\n renderingSpec: mongoDBRenderingSpecs.purchasesOverTimeYearly,\n dev: '60741378-0eac-4972-92d1-630c1fbf1c61',\n staging: '6578ab17-36db-4264-8411-d4da66522386',\n prod: '6578aa90-6a42-46bc-80ba-12a8d7f51d80'\n },\n dailyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.purchasesOverTimeWeekdayPeriodic,\n dev: 'd70d2ce5-bba4-42cd-8307-7a63c7fb5c92',\n staging: '6578ab17-36db-4a47-8645-d4da665223aa',\n prod: '6578aa90-6a42-451a-81e8-12a8d7f51da4'\n },\n hourlyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.purchasesOverTimeHourPeriodic,\n dev: 'c8b7c0b4-b5ea-4ebf-835f-fda8207aed01',\n staging: '6578ab17-36db-4464-8bfa-d4da665223ac',\n prod: '6578aa90-6a42-49ed-802d-12a8d7f51da6'\n }\n }\n },\n newStampCardsOverTime: {\n type: ChartTypes.LINE,\n filters: AllowedChartFilters.newStampCardsOverTime,\n defaultLayout: ChartDefaultLayouts.newStampCardsOverTime,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.newStampCardsOverTimeDaily,\n dev: '90dd98f4-ce47-4a02-952b-34f157604b0f',\n staging: '6578ab17-36db-47fc-847e-d4da6652238a',\n prod: '6578aa90-6a42-484a-836f-12a8d7f51d85'\n },\n hourly: {\n renderingSpec: mongoDBRenderingSpecs.newStampCardsOverTimeHourly,\n dev: '60b56e39-5846-47ae-aab7-639f3a70cc62',\n staging: '6578ab17-36db-4ba2-8df2-d4da66522388',\n prod: '6578aa90-6a42-46b3-8b6f-12a8d7f51d82'\n },\n daily: {\n renderingSpec: mongoDBRenderingSpecs.newStampCardsOverTimeDaily,\n dev: '90dd98f4-ce47-4a02-952b-34f157604b0f',\n staging: '6578ab17-36db-47fc-847e-d4da6652238a',\n prod: '6578aa90-6a42-484a-836f-12a8d7f51d85'\n },\n weekly: {\n renderingSpec: mongoDBRenderingSpecs.newStampCardsOverTimeWeekly,\n dev: '676aa4f8-ad30-4b2b-9b48-fdf0113865e8',\n staging: '6578ab17-36db-4d06-80f1-d4da6652238c',\n prod: '6578aa90-6a42-47c2-8a38-12a8d7f51d88'\n },\n monthly: {\n renderingSpec: mongoDBRenderingSpecs.newStampCardsOverTimeMonthly,\n dev: '345aa107-7aa6-4a47-a8ba-9f36a4facb1e',\n staging: '6578ab17-36db-46ee-8e61-d4da6652238e',\n prod: '6578aa90-6a42-4edd-87e5-12a8d7f51d8a'\n },\n yearly: {\n renderingSpec: mongoDBRenderingSpecs.newStampCardsOverTimeYearly,\n dev: '363a109a-7389-4c84-b837-944ef00b6df0',\n staging: '6578ab17-36db-4dd9-837c-d4da66522390',\n prod: '6578aa90-6a42-4fb7-8c48-12a8d7f51d8c'\n },\n dailyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.newStampCardsOverTimeWeekdayPeriodic,\n dev: '2959d16f-9084-495d-8e91-7e19b829588f',\n staging: '6578ab17-36db-47ba-8f69-d4da665223ae',\n prod: '6578aa90-6a42-4023-8346-12a8d7f51da8'\n },\n hourlyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.newStampCardsOverTimeHourPeriodic,\n dev: 'b5afbda5-efb9-4e49-a298-516ee2b8e216',\n staging: '6578ab17-36db-4c85-8b26-d4da665223b0',\n prod: '6578aa90-6a42-4bae-82de-12a8d7f51daa'\n }\n }\n },\n numberOfStampsOverTime: {\n type: ChartTypes.BAR,\n filters: AllowedChartFilters.numberOfStampsOverTime,\n defaultLayout: ChartDefaultLayouts.numberOfStampsOverTime,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimeDaily,\n dev: '0453ac1c-4e7a-4823-be08-5b947fecf45c',\n staging: '6578ab17-36db-4b74-8eee-d4da66522396',\n prod: '6578aa90-6a42-4e44-8e5d-12a8d7f51d92'\n },\n hourly: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimeHourly,\n dev: 'd4558bbe-abdb-4e45-a231-6e4ef42605e6',\n staging: '6578ab17-36db-4d0e-81b2-d4da66522394',\n prod: '6578aa90-6a42-41f0-898a-12a8d7f51d90'\n },\n daily: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimeDaily,\n dev: '0453ac1c-4e7a-4823-be08-5b947fecf45c',\n staging: '6578ab17-36db-4b74-8eee-d4da66522396',\n prod: '6578aa90-6a42-4e44-8e5d-12a8d7f51d92'\n },\n weekly: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimeWeekly,\n dev: 'f6ca3c03-d229-42e0-93c7-11da938f7760',\n staging: '6578ab17-36db-4aec-8809-d4da66522398',\n prod: '6578aa90-6a42-47cb-863a-12a8d7f51d94'\n },\n monthly: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimeMonthly,\n dev: '9cb20353-f402-4f8c-9d03-996d81ef5775',\n staging: '6578ab17-36db-4393-8b93-d4da6652239a',\n prod: '6578aa90-6a42-47ed-8a60-12a8d7f51d96'\n },\n yearly: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimeYearly,\n dev: '81937974-7899-4d97-83e3-306f7cc86559',\n staging: '6578ab17-36db-4cdd-815b-d4da6652239d',\n prod: '6578aa90-6a42-4905-81f6-12a8d7f51d98'\n },\n dailyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimeWeekdayPeriodic,\n dev: 'a31cc71a-67da-4041-a1aa-70bc567a612f',\n staging: '6578ab17-36db-4e12-8762-d4da665223b3',\n prod: '6578aa90-6a42-4157-8388-12a8d7f51dac'\n },\n hourlyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimeHourPeriodic,\n dev: '0ef68175-92b3-4df6-bc07-03f00707b544',\n staging: '6578ab17-36db-44a4-85f7-d4da665223b5',\n prod: '6578aa90-6a42-4627-8458-12a8d7f51dae'\n }\n }\n },\n numberOfStampsOverTimePerVenue: {\n type: ChartTypes.BAR,\n filters: AllowedChartFilters.numberOfStampsOverTimePerVenue,\n defaultLayout: ChartDefaultLayouts.numberOfStampsOverTimePerVenue,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimePerVenueDaily,\n dev: '6633624a-59f2-4d83-83bf-ed8f07c13f34',\n staging: '6633a37c-4bbe-4bc4-8f8f-ebe3ee5aaf5c',\n prod: '5d03b000-7fd2-47e0-af7a-6cfaf1ad5ba5'\n },\n hourly: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimePerVenueHourly,\n dev: '6633624a-59f2-4b50-8622-ed8f07c13f32',\n staging: '6633a37c-4bbe-49c5-8702-ebe3ee5aaf5a',\n prod: 'a8e747e3-a9ec-4e04-b38e-6bca8adf01a9'\n },\n daily: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimePerVenueDaily,\n dev: '6633624a-59f2-4d83-83bf-ed8f07c13f34',\n staging: '6633a37c-4bbe-4bc4-8f8f-ebe3ee5aaf5c',\n prod: '5d03b000-7fd2-47e0-af7a-6cfaf1ad5ba5'\n },\n weekly: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimePerVenueWeekly,\n dev: '6633624a-59f2-47ff-8ed0-ed8f07c13f36',\n staging: '6633a37c-4bbe-46f0-822e-ebe3ee5aaf5e',\n prod: '404ff659-c3e5-43d4-9480-5fc2dbadf5d8'\n },\n monthly: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimePerVenueMonthly,\n dev: '6633624a-59f2-43e4-8e9e-ed8f07c13f38',\n staging: '6633a37c-4bbe-4874-80cf-ebe3ee5aaf60',\n prod: 'a48d902c-97cd-49d3-a897-185aed7dfa9c'\n },\n yearly: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimePerVenueYearly,\n dev: '6633624a-59f2-4814-8ce7-ed8f07c13f3a',\n staging: '6633a37c-4bbe-4ccf-8bb8-ebe3ee5aaf62',\n prod: 'de54fb59-1b30-4fa8-afb4-4371bc39dda3'\n },\n dailyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimePerVenueWeekdayPeriodic,\n dev: '6633624a-59f2-487e-8f7f-ed8f07c13f3c',\n staging: '6633a37c-4bbe-4d08-8e5c-ebe3ee5aaf64',\n prod: 'a5e5e42d-29a2-4314-a6aa-c6724ef47419'\n },\n hourlyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsOverTimePerVenueHourPeriodic,\n dev: '6633624a-59f2-4e1f-8c3e-ed8f07c13f3e',\n staging: '6633a37c-4bbe-41be-8bce-ebe3ee5aaf66',\n prod: '9d72f554-da0d-42cd-b651-7d03d6a1c2ee'\n }\n }\n },\n numberOfStampsNewOrReturningOverTime: {\n type: ChartTypes.BAR,\n filters: AllowedChartFilters.numberOfStampsNewOrReturningOverTime,\n defaultLayout: ChartDefaultLayouts.numberOfStampsNewOrReturningOverTime,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsNewOrReturningOverTimeDaily,\n dev: '8f320ec3-8334-47c4-b03e-e379e41d8686',\n staging: '370d7197-530e-464c-ace1-8b0835239b33',\n prod: 'f44a7b58-3c23-41ca-81ea-5e73d3705414'\n },\n hourly: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsNewOrReturningOverTimeHourly,\n dev: '4de2d859-e344-479a-af16-5a7198d276fd',\n staging: 'f8c4cb1b-022f-44de-b09e-08532517acf6',\n prod: '88369bee-b65b-4120-b508-ec82dc2d2485'\n },\n daily: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsNewOrReturningOverTimeDaily,\n dev: '8f320ec3-8334-47c4-b03e-e379e41d8686',\n staging: '370d7197-530e-464c-ace1-8b0835239b33',\n prod: 'f44a7b58-3c23-41ca-81ea-5e73d3705414'\n },\n weekly: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsNewOrReturningOverTimeWeekly,\n dev: '5c553e8d-2f0a-401f-abf6-cc17cbe37d0f',\n staging: '3b719850-c1a7-4e1e-8b22-73f0a82a9320',\n prod: '8e473680-ef0c-4f8a-88f1-b8e1dd1c940b'\n },\n monthly: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsNewOrReturningOverTimeMonthly,\n dev: '0497e4c6-4552-47c8-bb9c-742948b5e4a5',\n staging: '5343014d-38dd-4241-add5-9a61e467d01a',\n prod: '9f276c93-dbbc-43f8-b1fd-507c3b539959'\n },\n yearly: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsNewOrReturningOverTimeYearly,\n dev: '2f682e6e-e75f-46df-84b6-60bb4877fec3',\n staging: '90d92e82-9a55-4eb7-9d1f-ff9acfb13cea',\n prod: ''\n },\n dailyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsNewOrReturningOverTimeWeekdayPeriodic,\n dev: 'f4b8ac06-59cf-47c8-8c98-5fb2a738ded3',\n staging: 'ba417944-d184-43ac-b55b-03f3b88d1794',\n prod: '9a183199-97a5-4ccb-8f5c-7093fb78ac1e'\n },\n hourlyPeriodic: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsNewOrReturningOverTimeHourPeriodic,\n dev: 'f96fa9a1-758b-44fc-ace4-0850c38a243b',\n staging: '58b74ed9-e998-4e53-99ff-9c5fae829a38',\n prod: '99bfa8d7-6484-4dee-8106-431a0626729c'\n }\n }\n },\n campaignSMSSentTotal: {\n type: ChartTypes.NUMERIC,\n filters: AllowedChartFilters.campaignSMSSentTotal,\n defaultLayout: ChartDefaultLayouts.campaignSMSSentTotal,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.campaignSMSSentTotal,\n dev: '020d39dd-a317-497b-9a1d-7e718a661c26',\n staging: '6578ab17-36db-472d-81bb-d4da66522376',\n prod: '6578aa90-6a42-4a89-8bc1-12a8d7f51d70'\n }\n }\n },\n SMSCampaignsTotal: {\n type: ChartTypes.NUMERIC,\n filters: AllowedChartFilters.SMSCampaignsTotal,\n defaultLayout: ChartDefaultLayouts.SMSCampaignsTotal,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.SMSCampaignsTotal,\n dev: '6488539e-7e88-42d5-83f9-8101db8cdd4b',\n staging: '6578ab17-36db-4580-880a-d4da66522370',\n prod: '6578aa90-6a42-46a8-8638-12a8d7f51d6a'\n }\n }\n },\n numberOfStampsTotal: {\n type: ChartTypes.NUMERIC,\n filters: AllowedChartFilters.numberOfStampsTotal,\n defaultLayout: ChartDefaultLayouts.numberOfStampsTotal,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsTotal,\n dev: 'd039c856-5520-4834-9284-4e0200bc56a0',\n staging: '6578ab17-36db-482e-83dc-d4da6652239f',\n prod: '6578aa90-6a42-4a04-8889-12a8d7f51d9a'\n }\n }\n },\n numberOfRegistrationsTotal: {\n type: ChartTypes.NUMERIC,\n filters: AllowedChartFilters.numberOfRegistrationsTotal,\n defaultLayout: ChartDefaultLayouts.numberOfRegistrationsTotal,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.numberOfRegistrationsTotal,\n dev: 'eb02f9f7-6237-4d58-bf86-cc013ce30bf5',\n staging: '6578ab17-36db-4c1a-8c49-d4da665223b9',\n prod: '6578aa90-6a42-4cb2-81c1-12a8d7f51db2'\n }\n }\n },\n numberOfUniqueRegistrationsTotal: {\n type: ChartTypes.NUMERIC,\n filters: AllowedChartFilters.numberOfUniqueRegistrationsTotal,\n defaultLayout: ChartDefaultLayouts.numberOfUniqueRegistrationsTotal,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.numberOfUniqueRegistrationsTotal,\n dev: '64fb429f-a9f5-4fe5-821f-f8f81687ee5a',\n staging: '6578ab17-36db-4bc8-8228-d4da665223b7',\n prod: '6578aa90-6a42-4287-8243-12a8d7f51db0'\n }\n }\n },\n stampCardsRewardsClaimedTotal: {\n type: ChartTypes.NUMERIC,\n filters: AllowedChartFilters.stampCardsRewardsClaimedTotal,\n defaultLayout: ChartDefaultLayouts.stampCardsRewardsClaimedTotal,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.stampCardsRewardsClaimedTotal,\n dev: '5cc1031f-dfb8-4655-bdc7-9d58f7b690ca',\n staging: '6578ab17-36db-4dd4-83e6-d4da66522374',\n prod: '6578aa90-6a42-4494-814d-12a8d7f51d6e'\n }\n }\n },\n stampCardsCreatedTotal: {\n type: ChartTypes.NUMERIC,\n filters: AllowedChartFilters.stampCardsCreatedTotal,\n defaultLayout: ChartDefaultLayouts.stampCardsCreatedTotal,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.stampCardsCreatedTotal,\n dev: '648855fa-fe03-47c9-8e74-9786d2539ec9',\n staging: '6578ab17-36db-4e2f-8fea-d4da66522372',\n prod: '6578aa90-6a42-4d50-8ad6-12a8d7f51d6c'\n }\n }\n },\n averageStampsPerStampCardDonut: {\n type: ChartTypes.DONUT,\n filters: AllowedChartFilters.averageStampsPerStampCardDonut,\n defaultLayout: ChartDefaultLayouts.averageStampsPerStampCardDonut,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.averageStampsPerStampCardDonut,\n dev: 'a0711600-deba-4476-be60-0fd0acfdf1a1',\n staging: '6578ab17-36db-417a-87f5-d4da665223a4',\n prod: '6578aa90-6a42-42f2-8419-12a8d7f51d9e'\n }\n }\n },\n numberOfStampsPerStampCardCampaignHistogram: {\n type: ChartTypes.HISTOGRAM,\n filters: AllowedChartFilters.numberOfStampsPerStampCardCampaignHistogram,\n defaultLayout: ChartDefaultLayouts.numberOfStampsPerStampCardCampaignHistogram,\n MongoDBRefsAndRenderingSpec: {\n default: {\n renderingSpec: mongoDBRenderingSpecs.numberOfStampsPerStampCardCampaignHistogram,\n dev: '10359420-07cc-4e79-90ca-09dc05763d5e',\n staging: '6578ab17-36db-4248-81c0-d4da66522392',\n prod: '6578aa90-6a42-47c4-8aa6-12a8d7f51d8e'\n }\n }\n }\n};\n","import { RenderingSpec } from '@mongodb-js/charts-embed-dom';\n\nimport { ChartsWithVariants } from './MongoDBWidgets.interfaces.names';\n\nexport const ChartTypes = {\n LINE: 'LINE',\n NUMERIC: 'NUMERIC',\n TABLE: 'TABLE',\n DONUT: 'DONUT',\n PIE: 'PIE',\n BAR: 'BAR',\n BAR_STACKED: 'BAR_STACKED',\n BAR_GROUPED: 'BAR_GROUPED',\n HISTOGRAM: 'HISTOGRAM',\n SCATTER: 'SCATTER'\n} as const;\n\nexport const CHART_VERSION = 1 as const;\n\nexport interface ChartPresetPerCountry {\n se: RenderingSpec;\n en: RenderingSpec;\n}\n\nexport interface ChartReferenceAndPreset {\n renderingSpec: ChartPresetPerCountry;\n dev: string;\n staging: string;\n prod: string;\n}\n\nexport type ChartReferenceEnvironments = keyof Omit;\n\nexport const timeBasedChartVariations = [\n 'default', // MUST ALWAYS BE PRESENT\n 'hourly',\n 'daily',\n 'weekly',\n 'monthly',\n 'yearly',\n 'hourlyPeriodic',\n 'dailyPeriodic'\n] as const;\nexport type TimeBasedChartVariations = (typeof timeBasedChartVariations)[number];\n\nexport const allowedMongoFilterSettings = [\n // Time based filters\n 'forever',\n 'createdAt',\n 'trailingTimeframe',\n 'hourlyPeriodic',\n 'dailyPeriodic',\n\n // Collection filters\n 'venueId',\n 'stampCardCampaignId',\n 'isFirst',\n 'owner',\n 'endpoint',\n 'state'\n] as const;\nexport type AllowedMongoFilterSettings = (typeof allowedMongoFilterSettings)[number];\n\nexport type MongoDBRenderingSpecs = {\n [key in keyof typeof ChartsWithVariants]: {\n [key2 in keyof ChartPresetPerCountry]: RenderingSpec & { title: string };\n };\n};\n","import { DashboardItemLayout } from 'views/Dashboard/Interfaces/Dashboard.layout';\n\nimport { GenerateLayout } from '../helpers';\nimport { PayAttWidgetNames } from './PayAttWidgets.names';\n\nconst SizeSettingsCalculations = { w: 10, h: 5, minW: 4, minH: 4 };\n\nexport const PayAttWidgetDefaultLayouts: { [key in keyof typeof PayAttWidgetNames]: DashboardItemLayout } = {\n sendSMSCampaign: GenerateLayout({ layoutType: PayAttWidgetNames.sendSMSCampaign, minW: 6, minH: 9, w: 9, h: 9 }),\n numberOfStampsAndStampCardsSentence: GenerateLayout({\n layoutType: PayAttWidgetNames.numberOfStampsAndStampCardsSentence,\n requiresStampCardStats: true,\n h: 3,\n w: 6,\n minW: 2,\n minH: 2\n }),\n increasedRevenueBasedOnVisits: GenerateLayout({\n layoutType: PayAttWidgetNames.increasedRevenueBasedOnVisits,\n ...SizeSettingsCalculations\n }),\n increasedRevenueBasedOnSMSCampaign: GenerateLayout({\n layoutType: PayAttWidgetNames.increasedRevenueBasedOnSMSCampaign,\n ...SizeSettingsCalculations\n }),\n increasedRevenueBasedOnStampCardCampaign: GenerateLayout({\n layoutType: PayAttWidgetNames.increasedRevenueBasedOnStampCardCampaign,\n ...SizeSettingsCalculations\n })\n};\n","import { RESPONSIVE_COLUMNS } from '../Interfaces/Dashboard.constants';\nimport { DashboardItemLayout } from '../Interfaces/Dashboard.layout';\nimport { AllChartFilters } from './MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.filters';\nimport { ChartNames } from './MongoDBWidgets/Interfaces/MongoDBWidgets.interfaces.names';\nimport { PayAttWidgetNames } from './PayAttWidgets/PayAttWidgets.names';\n\nexport interface GenerateLayoutProps {\n layoutType: keyof typeof PayAttWidgetNames | keyof typeof ChartNames;\n requiresStampCardStats?: boolean;\n minW?: number;\n minH?: number;\n maxW?: number;\n maxH?: number;\n w?: number;\n h?: number;\n x?: number;\n y?: number;\n isDraggable?: boolean;\n isResizable?: boolean;\n isStatic?: boolean;\n filter?: AllChartFilters;\n}\nexport const CloseGridItem = (\n e: React.MouseEvent,\n removeGridItem: (gridId: string) => void\n) => {\n // Traverse the DOM tree to find the parent element with the data-grid-id attribute\n let { parentElement } = e.currentTarget;\n while (parentElement && parentElement.getAttribute('data-grid-id') === null) {\n parentElement = parentElement.parentElement;\n }\n\n if (parentElement) {\n const gridId = parentElement.getAttribute('data-grid-id');\n\n if (gridId) removeGridItem(gridId);\n }\n};\n\nexport const GenerateLayout = ({\n layoutType,\n requiresStampCardStats = false,\n minW = 1,\n minH = 1,\n maxW = RESPONSIVE_COLUMNS.xl,\n maxH = 40,\n w = 9,\n h = 7,\n x = 0,\n y = 0,\n isDraggable = true,\n isResizable = true,\n isStatic = false,\n filter\n}: GenerateLayoutProps): DashboardItemLayout => {\n const item: DashboardItemLayout = {\n // i is calculated dynamically when added to dashboard\n i: '',\n layoutType,\n x,\n y,\n w,\n h,\n minW,\n minH,\n maxW,\n maxH,\n isDraggable,\n isResizable,\n static: isStatic,\n requiresStampCardStats,\n filter: filter || undefined\n };\n\n if (item.w > RESPONSIVE_COLUMNS.xl) {\n console.info(\n `Width of ${layoutType} is larger than the maximum number of columns for breakpoint xl, setting to ${RESPONSIVE_COLUMNS.xl}`\n );\n item.w = RESPONSIVE_COLUMNS.xl;\n } else if (item.w < 1) {\n console.info(`Width of ${layoutType} may not be negative, setting to 1`);\n item.w = 1;\n }\n\n if (item.minW > RESPONSIVE_COLUMNS.xl) {\n console.info(\n `Minimum width of ${layoutType} is larger than the maximum number of columns for breakpoint xl, setting to ${RESPONSIVE_COLUMNS.xl}`\n );\n item.minW = RESPONSIVE_COLUMNS.xl;\n } else if (item.minW < 1) {\n console.info(`Minimum width of ${layoutType} may not be negative, setting to 1`);\n item.minW = 1;\n }\n\n if (item.maxW > RESPONSIVE_COLUMNS.xl) {\n console.info(\n `Maximum width of ${layoutType} is larger than the minimum number of columns for breakpoint xl, setting to ${RESPONSIVE_COLUMNS.xl}`\n );\n item.maxW = RESPONSIVE_COLUMNS.xl;\n } else if (item.maxW < 1) {\n console.info(`Maximum width of ${layoutType} may not be negative, setting to 1`);\n item.minW = 1;\n }\n\n if (item.w < item.minW) {\n console.info(\n `Minimum width of ${layoutType} may not be bigger than width, setting minimum width to match width`\n );\n item.minW = item.w;\n }\n\n if (item.w > item.maxW) {\n console.info(\n `Maximum width of ${layoutType} may not be smaller than width, setting maximum width to match width`\n );\n item.maxW = item.w;\n }\n\n return item;\n};\n","import React from 'react';\nimport { TooltipRenderProps } from 'react-joyride';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Box, Button, Typography } from '@mui/material';\n\nimport { JOYRIDE_OPTIONS } from 'Joyride/JoyridePayAtt';\nimport LogoWhite from 'assets/images/PayAtt_logo_white.svg';\n\nimport { CenteredFlexBox, VerticalCenteredFlexBox } from 'generalComponents/BoxModifications';\n\nconst TooltipFooter: React.FC> = ({\n continuous,\n index,\n backProps,\n closeProps,\n primaryProps\n}) => {\n return (\n \n {index > 0 && (\n \n \n \n )}\n {continuous && (\n \n \n \n )}\n {!continuous && (\n \n \n \n )}\n \n );\n};\n\nexport const WelcomeTooltip: React.FC = ({ tooltipProps, ...rest }) => {\n const screenWidth = window.innerWidth;\n\n let bgImgWidth = 740;\n let bgImgHeight = 416;\n\n if (screenWidth <= bgImgWidth + 20) {\n bgImgWidth *= 0.7;\n bgImgHeight *= 0.7;\n }\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n );\n};\n","import { Step } from 'react-joyride';\nimport { Translate } from 'react-redux-i18n';\n\nimport { Box } from '@mui/material';\n\nimport { CreateIntroStep, HomepageMainIntroStepLength } from 'Joyride/JoyridePayAtt.constants';\n\nimport { CenteredFlexBox } from 'generalComponents/BoxModifications';\n\nimport { WelcomeTooltip } from './Home.WelcomeStep.Tooltip';\n\ntype HomepageIntroSteps = Tuple;\n\nconst createIntroStep = (count: number, step?: CreateIntroStep): Step => {\n return {\n ...step,\n disableBeacon: true,\n disableScrolling: step?.disableScrolling || true,\n target: `.home-intro-step-${count}`,\n title:\n step?.title === null\n ? undefined\n : step?.title || ,\n content:\n step?.content === null\n ? undefined\n : step?.content || \n };\n};\n\nexport const homeIntroSteps: HomepageIntroSteps = [\n createIntroStep(1, {\n placement: 'center',\n styles: { overlay: { backgroundColor: '#000000c2' } },\n tooltipComponent: (props) => \n }),\n createIntroStep(2, {\n spotlightClicks: true,\n placement: 'left-start',\n styles: { spotlight: { padding: '10px 10px 100px 10px' }, tooltipContent: { textAlign: 'left' } },\n title: (\n \n \n \n \n \n \n ),\n content: \n }),\n createIntroStep(3, { placement: 'left-start' }),\n createIntroStep(4, { placement: 'left-start', styles: { tooltipContent: { textAlign: 'left' } } }),\n createIntroStep(5, {\n placement: 'bottom',\n styles: { tooltip: { width: '700px', maxWidth: '80%' }, tooltipContent: { textAlign: 'left' } }\n }),\n createIntroStep(6, {\n placement: 'right',\n spotlightPadding: 0,\n styles: { tooltipContent: { textAlign: 'left' } },\n content: \n }),\n createIntroStep(7, { placement: 'left-start', styles: { tooltipContent: { textAlign: 'left' } } }),\n createIntroStep(8, { placement: 'right-end' }),\n createIntroStep(9, { placement: 'right', spotlightPadding: 0, styles: { tooltipContent: { textAlign: 'left' } } }),\n createIntroStep(10, {\n placement: 'right',\n spotlightPadding: 0,\n styles: { tooltip: { width: '700px', maxWidth: '80%' }, tooltipContent: { textAlign: 'left' } }\n }),\n createIntroStep(11, {\n placement: 'right',\n spotlightPadding: 0,\n styles: { tooltip: { width: '700px', maxWidth: '80%' }, tooltipContent: { textAlign: 'left' } }\n }),\n createIntroStep(12, { placement: 'right', spotlightPadding: 0, styles: { tooltipContent: { textAlign: 'left' } } }),\n createIntroStep(13, {\n placement: 'center',\n styles: { tooltipContent: { textAlign: 'left' } },\n content: \n })\n];\n\n// Start at 1, just like JoyRide step array\nexport const introStepHome = (val: number) => {\n const { target } = homeIntroSteps[val - 1];\n\n if (typeof target === 'string') return target.substring(1);\n\n throw new Error('Cannot call introStepHome on a step with a target set to JSX.Element');\n};\n","import { Step } from 'react-joyride';\nimport { Translate } from 'react-redux-i18n';\n\nimport { HomepageSettingsIntroStepLength } from 'Joyride/JoyridePayAtt.constants';\n\ntype HomepageSettingsIntroSteps = Tuple;\n\nconst createIntroStep = (\n count: number,\n step?: Omit & Partial>\n): Step => {\n return {\n ...step,\n disableBeacon: true,\n disableScrolling: step?.disableScrolling || true,\n target: `.homepage-settings-intro-step-${count}`,\n title:\n step?.title === null\n ? undefined\n : step?.title || ,\n content:\n step?.content === null\n ? undefined\n : step?.content || \n };\n};\n\nexport const homeWidgetMenuIntroSteps: HomepageSettingsIntroSteps = [\n createIntroStep(1, {\n placement: 'left',\n spotlightPadding: 0,\n styles: { tooltipContent: { textAlign: 'left' } }\n }),\n createIntroStep(2, {\n placement: 'left',\n spotlightClicks: true,\n spotlightPadding: 0,\n styles: { tooltipContent: { textAlign: 'left' } },\n content: \n }),\n createIntroStep(3, { placement: 'left-end', spotlightClicks: true }),\n createIntroStep(4, {\n placement: 'left-end',\n spotlightClicks: true,\n styles: { tooltipContent: { textAlign: 'left' } }\n }),\n createIntroStep(5, {\n placement: 'left-end',\n spotlightClicks: true,\n styles: { tooltipContent: { textAlign: 'left' } }\n }),\n createIntroStep(6, {\n placement: 'left-end',\n spotlightClicks: true,\n styles: { tooltipContent: { textAlign: 'left' } }\n }),\n createIntroStep(7, {\n placement: 'left-end',\n spotlightClicks: true,\n spotlightPadding: 0,\n styles: { tooltipContent: { textAlign: 'left' } },\n content: \n }),\n createIntroStep(8, {\n placement: 'top',\n spotlightPadding: 0,\n styles: { spotlight: { paddingBottom: '50px', marginTop: '-6px' } }\n })\n];\n\n// Start at 1, just like JoyRide step array. Returns the target without the leading period\nexport const introStepHomeSettings = (val: number) => {\n const { target } = homeWidgetMenuIntroSteps[val - 1];\n\n if (typeof target === 'string') return target.substring(1);\n\n throw new Error('Cannot call introStepHomeWidget on a step with a target set to JSX.Element');\n};\n","import { createContext, useCallback, useContext, useMemo, useState } from 'react';\nimport { ACTIONS, CallBackProps, EVENTS, STATUS } from 'react-joyride';\n\nexport const INTRO_WIDGET_MENU_CLASS_NAMES = {\n scrollContainer: 'payatt-widget-menu-scroll-container',\n backgroundImage: 'payatt-widget-menu-row-backgroundImage',\n trailingOrDatePickersSelector: 'payatt-widget-menu-row-trailingOrDatepickerSelector',\n venueSelector: 'payatt-widget-menu-row-venueSelector',\n stampCardSelector: 'payatt-widget-menu-row-campaignSelector',\n periodic: 'payatt-widget-menu-row-periodic',\n apply: 'payatt-widget-menu-row-submitButton'\n};\n\ninterface HomeWidgetMenuJoyrideContext {\n stepIndex: number;\n setStepIndex: React.Dispatch>;\n isRunning: boolean;\n setIsRunning: React.Dispatch>;\n isManuallyStartedIntro: boolean;\n setIsManuallyStartedIntro: React.Dispatch>;\n handleJoyrideCallback: (data: CallBackProps) => void;\n}\n\nconst scrollToElement = (elementClassName?: string) => {\n const container = document.querySelector(`.${INTRO_WIDGET_MENU_CLASS_NAMES.scrollContainer}`);\n\n if (!elementClassName && container) return container.scroll(0, 0);\n\n const child = document.querySelector(`.${elementClassName}`);\n if (container && child) {\n const heightToScroll = container.scrollTop + child.getBoundingClientRect().top;\n container.scroll(0, heightToScroll);\n }\n};\n\nexport const HomeWidgetMenuJoyrideContext = createContext(null);\nexport const HomeWidgetMenuJoyrideContextProvider: React.FC<{\n children: React.ReactNode;\n}> = ({ children }) => {\n const [stepIndex, setStepIndex] = useState(0);\n const [isRunning, setIsRunning] = useState(false);\n const [isManuallyStartedIntro, setIsManuallyStartedIntro] = useState(false);\n\n const handleJoyrideCallback = useCallback((data: CallBackProps) => {\n const { action, index, type, status } = data;\n\n if (EVENTS.STEP_BEFORE === type) {\n if (action === ACTIONS.NEXT || action === ACTIONS.PREV) {\n if (index === 1) scrollToElement();\n if (index === 2) scrollToElement();\n if (index === 3) scrollToElement(INTRO_WIDGET_MENU_CLASS_NAMES.trailingOrDatePickersSelector);\n if (index === 4) scrollToElement(INTRO_WIDGET_MENU_CLASS_NAMES.venueSelector);\n if (index === 5) scrollToElement(INTRO_WIDGET_MENU_CLASS_NAMES.stampCardSelector);\n if (index === 6) scrollToElement(INTRO_WIDGET_MENU_CLASS_NAMES.periodic);\n if (index === 7) scrollToElement(INTRO_WIDGET_MENU_CLASS_NAMES.apply);\n }\n }\n\n // Handle what to do when the tour is finished or skipped:\n if ([STATUS.FINISHED, STATUS.SKIPPED].includes(status as any)) {\n // Need to set our running state to false, so we can restart if we click start again.\n setIsRunning(false);\n setStepIndex(0);\n return;\n }\n\n // Handle what to do when a Joyride button is clicked (eg 'Next', close, 'Back', etc), or if a Joyride button is clicked and the target for the next step doesn't exist.\n if ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND].includes(type as any)) {\n const newIndex = index + (action === ACTIONS.PREV ? -1 : 1);\n setStepIndex(newIndex);\n }\n }, []);\n\n const value = useMemo(\n () => ({\n stepIndex,\n setStepIndex,\n isRunning,\n setIsRunning,\n isManuallyStartedIntro,\n setIsManuallyStartedIntro,\n handleJoyrideCallback\n }),\n [stepIndex, isRunning, isManuallyStartedIntro, handleJoyrideCallback]\n );\n\n return