import React, { useEffect, useState } from 'react'; // Import Polotno and third-party libraries import QRCode from 'qrcode'; import { observer } from 'mobx-react-lite'; import { SectionTab } from 'polotno/side-panel'; import type { StoreType } from 'polotno/model/store'; import { useDispatch, useSelector } from 'react-redux'; import { AppDispatch, RootState } from '../../../redux/store'; import { failure, success } from '../../../redux/actions/snackbarActions'; import { setQrUrl, setUtmSource, setUtmMedium, setUtmCampaignName, setCustomUtms, clearQrFields, setIsQR, setQrDialog, closeQrDialog, } from '../../../redux/actions/customQRCodeActions'; import { uploadFile } from '../../../redux/actions/templateActions'; // Utils import { MESSAGES } from '../../../utils/message'; import { validURL } from '../../../utils/helper'; import { DISALLOWED_DOMAINS, MERGE_UTM_PARAMS, emojiRegex, } from '../../../utils/constants'; //Components import GeneralSelect from '../../../components/GenericUIBlocks/GeneralSelect'; import QRCodeModal from './V2/QRCodeModal'; import DialogV2 from '../../../components/GenericUIBlocks/Dialog/V2'; // UI Components import Input from '../../../components/GenericUIBlocks/Input'; import Typography from '../../GenericUIBlocks/Typography'; // Icons import CustomQRIcon from '../../../assets/images/templates/custom-qr-section-icon'; import QRCodeIcon from '../../../assets/images/templates/qr-code'; // @ts-ignore import Edit from '../../../assets/images/templates/edit.svg'; // @ts-ignore import Archive from '../../../assets/images/templates/archive.svg'; // @ts-ignore import ConfirmCloseIcon from '../../../assets/images/modal-icons/confirm-close-icon'; // @ts-ignore import Actions from '../../../assets/images/templates/actions.svg' // styles import './styles.scss'; interface CustomQRProps { store: StoreType; allowSenderFields: any; allowPropertyFields: any; excludedFields: any; currentTheme?: string | null | undefined; onGetQRCodes?: (payload: any) => Promise; onDeleteQRCodes?: (id: string | number) => Promise; onUploadQRCode?: (payload: any) => Promise; onEditQRCode?: (payload: any) => Promise; } const cancelDialogStylesV2 = { maxWidth: '567px', minHeight: 'auto', padding: '40px', }; // define the new custom section const CustomQRCode = { name: 'QR-Section', Tab: (props: any) => ( ), // we need observer to update component automatically on any store changes Panel: observer( ({ store, allowSenderFields, allowPropertyFields, excludedFields, currentTheme, onGetQRCodes, onDeleteQRCodes, onUploadQRCode, onEditQRCode, }: CustomQRProps) => { // V2 State for API-driven functionality const [qrCodes, setQrCodes] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isLoadingMore, setIsLoadingMore] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [activeDropdown, setActiveDropdown] = useState< string | number | null >(null); const [isEditing, setIsEditing] = useState(false); const [selectedQR, setSelectedQR] = useState(null); const [openDeleteDialog, setOpenDeleteDialog] = useState(false); const [isLoadingDelete, setIsLoadingDelete] = useState(false); const [saveQrLoading, setSaveQrLoading] = useState(false); const [isActionsOpen, setIsActionsOpen] = useState< string | number | null >(null); const [actionsDropdownUpward, setActionsDropdownUpward] = useState(false); const qrCodesGridRef = React.useRef(null); const dropdownRefs = React.useRef>( new Map() ); const actionsRefs = React.useRef>( new Map() ); const loadingTimeoutRef = React.useRef(null); const isLoadingRef = React.useRef(false); const noteStyles = { fontWeight: '400', fontSize: '12px', color: '#000', width: '100%', maxWidth: '245px', margin: '16px 0 20px', }; const itemStyle = { margin: 0, fontSize: '12px', fontWeight: '300', }; const handleDialogChange = (model = '') => { if (dialog.open) { dispatch(closeQrDialog()); } else { dispatch(setQrDialog(true, model)); } }; const handleCloseDeleteDialog = () => { setOpenDeleteDialog(false); }; const handleOpenDeleteDialog = () => { setOpenDeleteDialog(true); }; const handleDialogClose = (model = '') => { dispatch(closeQrDialog()); setIsEditing(false); dispatch(setQrUrl('')); dispatch(setUtmSource('direct mail')); dispatch(setUtmMedium('QR Code')); dispatch(setUtmCampaignName('')); dispatch(setCustomUtms({})); }; const dialog = useSelector((state: RootState) => state.customQRCode.dialog); const url = useSelector((state: RootState) => state.customQRCode.url); const utmSource = useSelector( (state: RootState) => state.customQRCode.utmSource ); const utmMedium = useSelector( (state: RootState) => state.customQRCode.utmMedium ); const utmCampaignName = useSelector( (state: RootState) => state.customQRCode.utmCampaignName ); const customUtms = useSelector( (state: RootState) => state.customQRCode.customUtms ); const isQR = useSelector((state: RootState) => state.customQRCode.isQR); const dispatch: AppDispatch = useDispatch(); const defaultFields = useSelector( (state: RootState) => state.templates.defaultDynamicFields ); const defaultSenderFields = useSelector( (state: RootState) => state.templates.defaultSenderFields ); const customFields = useSelector( (state: RootState) => state.customFields.customFields ); const customFieldsV2 = useSelector( (state: RootState) => state.customFields.customFieldsV2 ) as Record; const defaultPropertyFields = useSelector( (state: RootState) => state.templates.defaultPropertyFields ); const excludedLabels = ['utm_c_first_name c_last_name']; let flattenedFieldsV2 = []; if (customFieldsV2.length > 0) { flattenedFieldsV2 = customFieldsV2?.flatMap( (section: { fields: any }) => section.fields ); } const allFields = [ ...defaultFields, ...customFields, ...flattenedFieldsV2, ...(allowSenderFields ? defaultSenderFields : []), ...(allowPropertyFields ? defaultPropertyFields : []), ...(allowPropertyFields ? [ { value: 'ROS.PROPERTY_OFFER', key: '{{ROS.PROPERTY_OFFER}}', defaultValue: '$123,456.00', }, ] : []), ].filter(({ key }) => !excludedFields?.includes(key)); const utmFields = allFields .map(({ key }) => ({ label: `utm_${key?.toLowerCase().replaceAll('.', '_').replaceAll(/[{}]/g, '')}`, })) .filter((utmField) => !excludedLabels.includes(utmField.label)); const utms = ['custom_utm_1', 'custom_utm_2', 'custom_utm_3']; const el = store.selectedElements[0]; const clearQRFields = () => { store.selectElements([]); dispatch(clearQrFields()); }; const appendUtmParameters = (utmField: string, defaultValue: string) => { let result = ''; const field = MERGE_UTM_PARAMS.find((field) => field.key === utmField); if (field) { result = `${field.value}=${defaultValue}`; } return result; }; const validateQRCode = () => { const validations = [ { value: utmSource, label: 'UTM Source' }, { value: utmMedium, label: 'UTM Medium' }, { value: utmCampaignName, label: 'UTM Campaign' }, ]; for (const { value, label } of validations) { if (value.length >= 150) { dispatch(failure(`${label} must be less than 150 characters`)); return false; } if (emojiRegex.test(value)) { dispatch(failure(`Emoji are not allowed in ${label}`)); return false; } } return true; }; const containsDisallowedDomains = (str: string) => { return DISALLOWED_DOMAINS.some((substring) => str.includes(substring)); }; // create svg image for QR code for input text const getQR = (text: string) => { return new Promise((resolve, reject) => { QRCode.toDataURL( text || 'no-data', { type: 'image/png', margin: 0, color: { dark: '#000000', light: '#0000', // transparent background }, scale: 10, // increase for higher resolution }, (err: any, url: string) => { if (err) return reject(err); resolve(url); // returns base64 PNG image string } ); }); }; // Converts base64 data URL to File const base64ToFile = (base64: string, filename: string): File => { const arr = base64.split(','); const mime = arr[0].match(/:(.*?);/)?.[1] || 'image/png'; const bstr = atob(arr[1]); let n = bstr.length; const u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new File([u8arr], filename, { type: mime }); }; const createCustomizeURL = (url: string) => { let customURL = url; let params = []; if (utmSource) { params.push(`utm_source=${utmSource.replace(/ /g, '_')}`); } if (utmMedium) { params.push(`utm_medium=${utmMedium.replace(/ /g, '_')}`); } if (utmCampaignName) { params.push(`utm_campaign=${utmCampaignName.replace(/ /g, '_')}`); } if (customUtms[utms[0]]?.label) { const orignalField = `{{${customUtms[utms[0]]?.label.replace('utm_', '').replace('_', '.').toUpperCase()}}}`; const defaultValue = allFields.find( (field) => field.key === orignalField )?.defaultValue; const attachUtmKeys = appendUtmParameters(orignalField, defaultValue); const mergeUtmParams = attachUtmKeys && orignalField == '{{C.PHONE_NUMBER}}' ? encodeURIComponent( `${customUtms[utms[0]]?.label}=${defaultValue}&${attachUtmKeys}` ).replace(/[\s\(\)]/g, '') : attachUtmKeys ? `${customUtms[utms[0]]?.label}=${defaultValue}&${attachUtmKeys}` : `${customUtms[utms[0]]?.label}=${defaultValue}`; params.push(mergeUtmParams); } if (customUtms[utms[1]]?.label) { const orignalField = `{{${customUtms[utms[1]]?.label.replace('utm_', '').replace('_', '.').toUpperCase()}}}`; const defaultValue = allFields.find( (field) => field.key === orignalField )?.defaultValue; const attachUtmKeys = appendUtmParameters(orignalField, defaultValue); const mergeUtmParams = attachUtmKeys && orignalField == '{{C.PHONE_NUMBER}}' ? encodeURIComponent( `${customUtms[utms[1]]?.label}=${defaultValue}&${attachUtmKeys}` ).replace(/[\s\(\)]/g, '') : attachUtmKeys ? `${customUtms[utms[1]]?.label}=${defaultValue}&${attachUtmKeys}` : `${customUtms[utms[1]]?.label}=${defaultValue}`; params.push(mergeUtmParams); } if (customUtms[utms[2]]?.label) { const orignalField = `{{${customUtms[utms[2]]?.label.replace('utm_', '').replace('_', '.').toUpperCase()}}}`; const defaultValue = allFields.find( (field) => field.key === orignalField )?.defaultValue; const attachUtmKeys = appendUtmParameters(orignalField, defaultValue); const mergeUtmParams = attachUtmKeys && orignalField == '{{C.PHONE_NUMBER}}' ? encodeURIComponent( `${customUtms[utms[2]]?.label}=${defaultValue}&${attachUtmKeys}` ).replace(/[\s\(\)]/g, '') : attachUtmKeys ? `${customUtms[utms[2]]?.label}=${defaultValue}&${attachUtmKeys}` : `${customUtms[utms[2]]?.label}=${defaultValue}`; params.push(mergeUtmParams); } if (params.length > 0) { customURL += `/?${params.join('&')}`; } return encodeURI(customURL); }; const addNewQRCode = async () => { if (utmCampaignName) { if (url) { if (validURL(url) && !containsDisallowedDomains(url)) { const isValidQR = validateQRCode(); if (!isValidQR) return false; setSaveQrLoading(true); const randomizedId = Math.random().toString(36).substring(2, 7); const customQRUrl = createCustomizeURL(url); const src = await getQR(customQRUrl); if (currentTheme === 'v2' && onUploadQRCode && onGetQRCodes) { const file = base64ToFile(src, `${utmCampaignName}-qr.png`); const uploadedFile = await uploadFile(file); if (!uploadedFile) { setSaveQrLoading(false); return dispatch(failure(MESSAGES.TEMPLATE.QR_SECTION.FAILED_QR)); } const payload = { name: utmCampaignName, qrCodeJson: { name: utmCampaignName, url: url, qrImagePath: uploadedFile, utm_source: utmSource, utm_medium: utmMedium, utm_campaign_name: utmCampaignName, custom_utms: customUtms, }, }; try { await onUploadQRCode(payload); // Reset pagination and reload from first page setCurrentPage(1); setHasMore(true); await loadQRCodes(1, false, false); handleDialogClose(); // Close modal clearQRFields(); setSaveQrLoading(false); } catch (error) { setSaveQrLoading(false); console.error('Failed to create QR code:', error); } } else { store.activePage.addElement({ id: `qr-${randomizedId}`, type: 'image', name: 'qr', x: 50, y: 50, width: 100, height: 100, blurRadius: 0, keepRatio: true, src, custom: { url, utm_source: utmSource, utm_medium: utmMedium, utm_campaign_name: utmCampaignName, custom_utms: customUtms, }, }); clearQRFields(); } setSaveQrLoading(false); } else { dispatch(failure(MESSAGES.TEMPLATE.QR_SECTION.INVALID_URL)); } } else { dispatch(failure(MESSAGES.TEMPLATE.QR_SECTION.EMPTY_QR)); } } else { dispatch(failure(MESSAGES.TEMPLATE.QR_SECTION.EMPTY_CAMPAIGN)); } }; // V2 API Functions const loadQRCodes = React.useCallback( async (page: number = 1, append: boolean = false, loading = true) => { if (!onGetQRCodes) return; if (isLoadingRef.current) return; isLoadingRef.current = true; if (page === 1 && loading) { setIsLoading(true); } else { setIsLoadingMore(true); } try { const payload = { page, pageSize: 15, }; const qrCodesData = await onGetQRCodes(payload); if (qrCodesData && Array.isArray(qrCodesData.rows)) { const normalized = qrCodesData.rows.map((qr: any) => ({ id: qr.id, name: qr?.qrCodeJson?.name || qr?.qrCodeJson?.utm_campaign_name || '', url: qr.qrCodeJson.url, qrImagePath: qr.qrCodeJson.qrImagePath, status: qr.status, utm_source: qr.qrCodeJson.utm_source, utm_medium: qr.qrCodeJson.utm_medium, utm_campaign_name: qr.qrCodeJson.utm_campaign_name, custom_utms: qr.qrCodeJson.custom_utms, })); if (append && page > 1) { setQrCodes((prevQrCodes) => { const existingIds = new Set( prevQrCodes.map((qr: any) => qr.id) ); const newQrCodes = normalized.filter( (qr: any) => !existingIds.has(qr.id) ); return [...prevQrCodes, ...newQrCodes]; }); } else { setQrCodes(normalized); } const hasMorePages = qrCodesData.currentPage < qrCodesData.lastPage; setHasMore(hasMorePages); setCurrentPage(qrCodesData.currentPage); } else { if (!append) { setQrCodes([]); } setHasMore(false); } } catch (error) { console.error('Failed to load QR codes:', error); } finally { setIsLoading(false); setIsLoadingMore(false); isLoadingRef.current = false; } }, [onGetQRCodes] ); const loadMoreQRCodes = React.useCallback(async () => { if (!hasMore || isLoadingMore || isLoading) return; await loadQRCodes(currentPage + 1, true); }, [hasMore, isLoadingMore, isLoading, currentPage, loadQRCodes]); const handleScroll = React.useCallback(() => { if (!qrCodesGridRef.current || !hasMore || isLoadingMore || isLoading) return; const { scrollTop, scrollHeight, clientHeight } = qrCodesGridRef.current; const scrollPercentage = (scrollTop + clientHeight) / scrollHeight; if (scrollPercentage > 0.75) { if (loadingTimeoutRef.current) { clearTimeout(loadingTimeoutRef.current); } loadingTimeoutRef.current = setTimeout(() => { loadMoreQRCodes(); }, 300); } }, [hasMore, isLoadingMore, isLoading, loadMoreQRCodes]); const handleQRCodeSelect = async (qrCode: any) => { let customQRUrl = ''; let src: any = ''; let customData = {}; if (currentTheme === 'v2') { customQRUrl = createCustomizeURL(qrCode.url); src = qrCode.qrImagePath; customData = { url: qrCode.url, qrCodeInstanceId: qrCode.id, qrImagePath: qrCode.qrImagePath, utm_source: qrCode.utm_source, utm_medium: qrCode.utm_medium, utm_campaign_name: qrCode.utm_campaign_name, custom_utms: qrCode.custom_utms, } } else { customQRUrl = createCustomizeURL(qrCode.url); src = await getQR(customQRUrl); customData = { url: qrCode.url, utm_source: qrCode.utmSource, utm_medium: qrCode.utmMedium, utm_campaign_name: qrCode.utmCampaign, custom_utms: qrCode.customUtms, } } const randomizedId = Math.random().toString(36).substring(2, 7); store.activePage.addElement({ id: `qr-${randomizedId}`, type: 'image', name: 'qr', x: 50, y: 50, width: 100, height: 100, blurRadius: 0, keepRatio: true, src, custom: customData, }); }; const handleDeleteQRCode = async (qrCodeId: string | number) => { if (!onDeleteQRCodes) return; setIsLoadingDelete(true); try { await onDeleteQRCodes(qrCodeId); setQrCodes((prev) => prev.filter((qr) => qr.id !== qrCodeId)); setActiveDropdown(null); setIsActionsOpen(null); } catch (error) { console.error('Failed to delete QR code:', error); } finally { setIsLoadingDelete(false); } }; const handleEditQRCode = async (qrCode: any) => { setIsEditing(true); setSelectedQR(qrCode); dispatch(setQrUrl(qrCode.url ?? '')); dispatch(setUtmSource(qrCode.utm_source ?? '')); dispatch(setUtmMedium(qrCode.utm_medium ?? '')); dispatch(setUtmCampaignName(qrCode.utm_campaign_name ?? '')); dispatch(setCustomUtms(qrCode.custom_utms ?? {})); handleDialogChange('qr-modal'); setIsActionsOpen(null); }; const toggleActions = ( qrCodeId: string | number, event: React.MouseEvent ) => { event.stopPropagation(); const isOpening = isActionsOpen !== qrCodeId; if (isOpening) { const triggerEl = actionsRefs.current.get(qrCodeId); const scrollContainer = qrCodesGridRef.current; const dropdownHeight = 80; if (triggerEl && scrollContainer) { const triggerRect = triggerEl.getBoundingClientRect(); const containerRect = scrollContainer.getBoundingClientRect(); const spaceBelow = containerRect.bottom - triggerRect.bottom; setActionsDropdownUpward(spaceBelow < dropdownHeight); } else { setActionsDropdownUpward(false); } } setIsActionsOpen(isActionsOpen === qrCodeId ? null : qrCodeId); }; // if selection is changed we need to update input value const updateQRCode = async () => { if (url) { if (validURL(url) && !containsDisallowedDomains(url)) { const isValidQR = validateQRCode(); if (!isValidQR) return false; setSaveQrLoading(true); const customQRUrl = createCustomizeURL(url); const src = await getQR(customQRUrl); if (currentTheme === 'v2' && onEditQRCode && onGetQRCodes) { const file = base64ToFile(src, `${utmCampaignName}-qr.png`); const uploadedFile = await uploadFile(file); const payload = { name: utmCampaignName, id: selectedQR?.id, qrCodeJson: { name: utmCampaignName, url: url, qrImagePath: uploadedFile, utm_source: utmSource || null, utm_medium: utmMedium || null, utm_campaign_name: utmCampaignName, custom_utms: customUtms, }, }; try { await onEditQRCode(payload); // Reset pagination and reload from first page setCurrentPage(1); setHasMore(true); await loadQRCodes(1, false, false); handleDialogClose(); // Close modal clearQRFields(); setSaveQrLoading(false); } catch (error) { setSaveQrLoading(false); console.error('Failed to create QR code:', error); } } else if (el?.name === 'qr' && url) { el.set({ src, custom: { url, utm_source: utmSource, utm_medium: utmMedium, utm_campaign_name: utmCampaignName, custom_utms: customUtms, }, }); clearQRFields(); setSaveQrLoading(false); } } else { dispatch(failure(MESSAGES.TEMPLATE.QR_SECTION.INVALID_URL)); } } else { dispatch(failure(MESSAGES.TEMPLATE.QR_SECTION.EMPTY_QR)); } }; // Handler to update dropdown values const handleSelect = (utmKey: string, value: any) => { const updatedUtms = { ...customUtms }; if (value === null) { delete updatedUtms[utmKey]; } else { updatedUtms[utmKey] = value; } dispatch(setCustomUtms(updatedUtms)); }; // if selection is changed we need to update input value useEffect(() => { if (el?.name === 'qr' && currentTheme !== 'v2') { dispatch(setIsQR(el?.name === 'qr')); dispatch(setQrUrl(el?.custom?.url || el?.custom?.value || '')); dispatch(setUtmSource(el?.custom?.utm_source || 'direct mail')); dispatch(setUtmMedium(el?.custom?.utm_medium || 'QR Code')); dispatch(setUtmCampaignName(el?.custom?.utm_campaign_name || '')); if (Object.values(el?.custom?.custom_utms || {}).length) { dispatch(setCustomUtms(el?.custom?.custom_utms)); } else { dispatch(setCustomUtms({})); } } else if (isQR && el?.name !== 'qr') { dispatch(clearQrFields()); } }, [isQR, el]); // Handle click outside to close actions menu useEffect(() => { const handleClickOutside = (event: MouseEvent) => { // Close actions if clicking outside the currently open actions container if (isActionsOpen !== null) { const actionsElement = actionsRefs.current.get(isActionsOpen); if ( actionsElement && !actionsElement.contains(event.target as Node) ) { setIsActionsOpen(null); } } // V2: Handle dropdowns if (activeDropdown) { const dropdownElement = dropdownRefs.current.get(activeDropdown); if ( dropdownElement && !dropdownElement.contains(event.target as Node) ) { setActiveDropdown(null); } } }; if (isActionsOpen !== null || activeDropdown !== null) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [isActionsOpen, activeDropdown]); // V2: Load QR codes on mount if callbacks are provided useEffect(() => { if (onGetQRCodes) { loadQRCodes(1, false); } }, [onGetQRCodes, loadQRCodes]); // V2: Setup scroll event listener on the list container useEffect(() => { const listElement = qrCodesGridRef.current; if (listElement && onGetQRCodes) { listElement.addEventListener('scroll', handleScroll); return () => { listElement.removeEventListener('scroll', handleScroll); if (loadingTimeoutRef.current) { clearTimeout(loadingTimeoutRef.current); } }; } }, [ hasMore, isLoadingMore, isLoading, currentPage, handleScroll, onGetQRCodes, ]); return ( <> {currentTheme === 'v2' ? ( <>
handleDialogChange('qr-modal')} >
{MESSAGES.TEMPLATE.QR_SECTION.QR_NOTE} {/* V2 QR Codes List when callbacks are provided */} {onGetQRCodes && (
{isLoading ? (
Loading QR codes...
) : qrCodes.length === 0 ? (
No QR codes created yet
) : ( <> {qrCodes.map((qrCode) => (
handleQRCodeSelect(qrCode)}> code
{qrCode.name} {qrCode.status}
{(onEditQRCode || onDeleteQRCodes) &&
{ if (el) actionsRefs.current.set(qrCode.id, el); }} >
{ toggleActions(qrCode.id, e); e.stopPropagation(); }} > actions
{/* Actions */} {isActionsOpen === qrCode.id && (
{onEditQRCode && (
{ e.stopPropagation(); handleEditQRCode(qrCode); }} > edit Edit
)} {onDeleteQRCodes && (
{ e.stopPropagation(); handleOpenDeleteDialog(); setSelectedQR(qrCode.id); }} > Archive Archive
)}
)}
}
))} {/* Loading indicator for pagination */} {isLoadingMore && (
Loading more QR codes...
)} )}
)}
{dialog.open && ( )} {openDeleteDialog && ( } customStyles={cancelDialogStylesV2} open={openDeleteDialog} handleClose={handleCloseDeleteDialog} title='Delete QR Code' subHeading='' description='Are you sure you want to delete this QR code?' onSubmit={() => { handleDeleteQRCode(selectedQR); handleCloseDeleteDialog(); }} onCancel={handleCloseDeleteDialog} cancelText='No' submitText='Yes' isGallery={false} loading={isLoadingDelete} />)} ) : ( <>
{ dispatch(setQrUrl(e.target.value)); }} placeholder={MESSAGES.TEMPLATE.QR_SECTION.QR_PLACEHOLDER} value={url} qrField={true} />
{ dispatch(setUtmSource(e.target.value)); }} placeholder={'Enter UTM Source'} value={utmSource} qrField={true} />
{ dispatch(setUtmMedium(e.target.value)); }} placeholder={'Enter UTM Medium'} value={utmMedium} qrField={true} />
{ dispatch(setUtmCampaignName(e.target.value)); }} placeholder={'Enter UTM Campaign Name'} value={utmCampaignName} qrField={true} />
{utms?.map((utm, idx) => { return (
handleSelect(utm, value) } selectedValue={customUtms[utm] || (null as any)} builderSelect={true} clearField={true} search={true} qrField={true} isError={false} gallerySelect={false} />
); })} )} ); } ), }; export default CustomQRCode;