@openlettermarketing/olc-react-sdk 2.1.4 → 2.1.5-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/.eslintignore +1 -0
  2. package/.eslintrc.cjs +18 -0
  3. package/.eslintrc.yml +47 -0
  4. package/.github/workflows/publish-beta.yml +154 -0
  5. package/.github/workflows/publish-production.yml +143 -0
  6. package/.prettierignore +3 -0
  7. package/.prettierrc.yml +5 -0
  8. package/CHANGELOG.md +4 -0
  9. package/babel.config.json +10 -0
  10. package/build/index.js +82 -82
  11. package/build/index.js.map +1 -1
  12. package/build/types/version.d.ts +1 -1
  13. package/examples/.eslintrc.yml +4 -0
  14. package/index.html +18 -0
  15. package/package.json +1 -1
  16. package/public/vite.svg +1 -0
  17. package/src/App.tsx +209 -0
  18. package/src/assets/Fonts/Lexi-Regular.ttf +0 -0
  19. package/src/assets/images/create-template/prebuilt.svg +13 -0
  20. package/src/assets/images/create-template/scratch.svg +4 -0
  21. package/src/assets/images/input/cancel.tsx +20 -0
  22. package/src/assets/images/input/search.tsx +20 -0
  23. package/src/assets/images/input/select-cancel.tsx +17 -0
  24. package/src/assets/images/modal-icons/add.tsx +36 -0
  25. package/src/assets/images/modal-icons/cancel-file.tsx +12 -0
  26. package/src/assets/images/modal-icons/cancel-input.tsx +13 -0
  27. package/src/assets/images/modal-icons/cancel.tsx +35 -0
  28. package/src/assets/images/modal-icons/close-new.svg +3 -0
  29. package/src/assets/images/modal-icons/confirm-close-icon.tsx +14 -0
  30. package/src/assets/images/modal-icons/confirm-new.tsx +22 -0
  31. package/src/assets/images/modal-icons/confirm.svg +12 -0
  32. package/src/assets/images/modal-icons/cross.tsx +23 -0
  33. package/src/assets/images/modal-icons/del.tsx +19 -0
  34. package/src/assets/images/modal-icons/design-icon.tsx +22 -0
  35. package/src/assets/images/modal-icons/doc.tsx +43 -0
  36. package/src/assets/images/modal-icons/docx.tsx +43 -0
  37. package/src/assets/images/modal-icons/envelope-icon.tsx +26 -0
  38. package/src/assets/images/modal-icons/info.tsx +19 -0
  39. package/src/assets/images/modal-icons/jpeg.tsx +43 -0
  40. package/src/assets/images/modal-icons/jpg.tsx +43 -0
  41. package/src/assets/images/modal-icons/modal-cros.svg +4 -0
  42. package/src/assets/images/modal-icons/modal-cross.tsx +37 -0
  43. package/src/assets/images/modal-icons/new-cancel.tsx +11 -0
  44. package/src/assets/images/modal-icons/order-download.tsx +42 -0
  45. package/src/assets/images/modal-icons/pdf.tsx +51 -0
  46. package/src/assets/images/modal-icons/png.tsx +43 -0
  47. package/src/assets/images/modal-icons/save.tsx +23 -0
  48. package/src/assets/images/modal-icons/template-copy.tsx +25 -0
  49. package/src/assets/images/modal-icons/tool-cancel.tsx +25 -0
  50. package/src/assets/images/products/bi-new.svg +23 -0
  51. package/src/assets/images/products/left-arrow.svg +17 -0
  52. package/src/assets/images/products/personal-new.tsx +31 -0
  53. package/src/assets/images/products/postcard-new.tsx +27 -0
  54. package/src/assets/images/products/professional-new.tsx +24 -0
  55. package/src/assets/images/products/real-new.tsx +30 -0
  56. package/src/assets/images/products/right-arrow.svg +17 -0
  57. package/src/assets/images/products/snap-new.svg +31 -0
  58. package/src/assets/images/templates/actions.svg +3 -0
  59. package/src/assets/images/templates/address-block-icon.tsx +62 -0
  60. package/src/assets/images/templates/archive.svg +3 -0
  61. package/src/assets/images/templates/arrow-down.tsx +27 -0
  62. package/src/assets/images/templates/back-arrow.tsx +19 -0
  63. package/src/assets/images/templates/bi-fold-self-mailers.tsx +28 -0
  64. package/src/assets/images/templates/check.svg +3 -0
  65. package/src/assets/images/templates/code.svg +10 -0
  66. package/src/assets/images/templates/content-copy-icon.tsx +24 -0
  67. package/src/assets/images/templates/custom-add-on-icon.tsx +18 -0
  68. package/src/assets/images/templates/custom-qr-section-icon.tsx +9 -0
  69. package/src/assets/images/templates/custom-template.tsx +23 -0
  70. package/src/assets/images/templates/designer.tsx +43 -0
  71. package/src/assets/images/templates/dot.tsx +22 -0
  72. package/src/assets/images/templates/download-v2.svg +4 -0
  73. package/src/assets/images/templates/download.svg +4 -0
  74. package/src/assets/images/templates/dummy-template.tsx +76 -0
  75. package/src/assets/images/templates/dynamic-field.tsx +119 -0
  76. package/src/assets/images/templates/edit-pencil-icon.tsx +21 -0
  77. package/src/assets/images/templates/edit.svg +3 -0
  78. package/src/assets/images/templates/epo-icon.tsx +16 -0
  79. package/src/assets/images/templates/field.tsx +29 -0
  80. package/src/assets/images/templates/gsv-icon.tsx +31 -0
  81. package/src/assets/images/templates/info-icon.tsx +37 -0
  82. package/src/assets/images/templates/left-arrow.svg +17 -0
  83. package/src/assets/images/templates/pencil.svg +3 -0
  84. package/src/assets/images/templates/personal-letter.tsx +53 -0
  85. package/src/assets/images/templates/postcard.tsx +32 -0
  86. package/src/assets/images/templates/professional-letter.tsx +53 -0
  87. package/src/assets/images/templates/qr-code.tsx +13 -0
  88. package/src/assets/images/templates/real-penned-letters.tsx +57 -0
  89. package/src/assets/images/templates/right-arrow.svg +17 -0
  90. package/src/assets/images/templates/size-image-lg.tsx +20 -0
  91. package/src/assets/images/templates/size-image-mid.tsx +20 -0
  92. package/src/assets/images/templates/size-image-xl.tsx +20 -0
  93. package/src/assets/images/templates/size-image.tsx +20 -0
  94. package/src/assets/images/templates/snap-pack.tsx +67 -0
  95. package/src/assets/images/templates/template-default-design.tsx +21 -0
  96. package/src/assets/images/templates/trash-upload.svg +3 -0
  97. package/src/assets/images/templates/trash.svg +3 -0
  98. package/src/assets/images/templates/tri-fold-self-mailers.tsx +93 -0
  99. package/src/assets/images/templates/upload-image.svg +10 -0
  100. package/src/assets/images/templates/x.svg +3 -0
  101. package/src/assets/images/thumbnails/one.svg +9 -0
  102. package/src/assets/images/tooltip/tool-arrow.tsx +25 -0
  103. package/src/components/CreateTemplate/V2/index.tsx +525 -0
  104. package/src/components/CreateTemplate/V2/styles.scss +372 -0
  105. package/src/components/CreateTemplate/index.tsx +508 -0
  106. package/src/components/CreateTemplate/styles.scss +404 -0
  107. package/src/components/GenericUIBlocks/Button/index.tsx +54 -0
  108. package/src/components/GenericUIBlocks/Button/styles.scss +43 -0
  109. package/src/components/GenericUIBlocks/CircularProgress/index.tsx +18 -0
  110. package/src/components/GenericUIBlocks/CircularProgress/styles.scss +93 -0
  111. package/src/components/GenericUIBlocks/CustomTooltip/index.tsx +88 -0
  112. package/src/components/GenericUIBlocks/CustomTooltip/styles.scss +19 -0
  113. package/src/components/GenericUIBlocks/Dialog/V2/index.tsx +227 -0
  114. package/src/components/GenericUIBlocks/Dialog/V2/styles.scss +289 -0
  115. package/src/components/GenericUIBlocks/Dialog/index.tsx +185 -0
  116. package/src/components/GenericUIBlocks/Dialog/styles.scss +227 -0
  117. package/src/components/GenericUIBlocks/Divider/index.tsx +12 -0
  118. package/src/components/GenericUIBlocks/Divider/styles.scss +7 -0
  119. package/src/components/GenericUIBlocks/GeneralSelect/index.tsx +114 -0
  120. package/src/components/GenericUIBlocks/GeneralSelect/styles.scss +406 -0
  121. package/src/components/GenericUIBlocks/GeneralTooltip/index.tsx +25 -0
  122. package/src/components/GenericUIBlocks/GeneralTooltip/styles.scss +20 -0
  123. package/src/components/GenericUIBlocks/GenericSnackbar/Toast/index.tsx +91 -0
  124. package/src/components/GenericUIBlocks/GenericSnackbar/Toast/styles.scss +92 -0
  125. package/src/components/GenericUIBlocks/Grid/index.tsx +82 -0
  126. package/src/components/GenericUIBlocks/Input/index.tsx +269 -0
  127. package/src/components/GenericUIBlocks/Input/styles.scss +332 -0
  128. package/src/components/GenericUIBlocks/Tabs/index.tsx +71 -0
  129. package/src/components/GenericUIBlocks/Tabs/styles.scss +42 -0
  130. package/src/components/GenericUIBlocks/Typography/index.tsx +18 -0
  131. package/src/components/GenericUIBlocks/Typography/styles.scss +27 -0
  132. package/src/components/SidePanel/CustomAddOns/index.tsx +342 -0
  133. package/src/components/SidePanel/CustomAddOns/styles.scss +86 -0
  134. package/src/components/SidePanel/CustomBlockColors/index.tsx +211 -0
  135. package/src/components/SidePanel/CustomBlockColors/styles.scss +80 -0
  136. package/src/components/SidePanel/CustomFields/customFieldSection.tsx +547 -0
  137. package/src/components/SidePanel/CustomFields/styles.scss +64 -0
  138. package/src/components/SidePanel/CustomQRCode/V2/QRCodeModal/index.tsx +172 -0
  139. package/src/components/SidePanel/CustomQRCode/V2/QRCodeModal/styles.scss +46 -0
  140. package/src/components/SidePanel/CustomQRCode/index.tsx +1070 -0
  141. package/src/components/SidePanel/CustomQRCode/styles.scss +149 -0
  142. package/src/components/SidePanel/CustomUploads/V2/index.tsx +542 -0
  143. package/src/components/SidePanel/CustomUploads/V2/styles.scss +267 -0
  144. package/src/components/SidePanel/CustomUploads/index.tsx +301 -0
  145. package/src/components/SidePanel/Templates/ModalGallery/HireDesigner/index.tsx +424 -0
  146. package/src/components/SidePanel/Templates/ModalGallery/HireDesigner/styles.scss +180 -0
  147. package/src/components/SidePanel/Templates/ModalGallery/V2/index.tsx +235 -0
  148. package/src/components/SidePanel/Templates/ModalGallery/V2/styles.scss +244 -0
  149. package/src/components/SidePanel/Templates/ModalGallery/index.tsx +231 -0
  150. package/src/components/SidePanel/Templates/SideBarGallery/index.tsx +233 -0
  151. package/src/components/SidePanel/Templates/SideBarGallery/styles.scss +152 -0
  152. package/src/components/SidePanel/Templates/TemplatesCard/V2/index.tsx +149 -0
  153. package/src/components/SidePanel/Templates/TemplatesCard/V2/styles.scss +156 -0
  154. package/src/components/SidePanel/Templates/TemplatesCard/index.tsx +160 -0
  155. package/src/components/SidePanel/Templates/TemplatesCard/styles.scss +98 -0
  156. package/src/components/SidePanel/Templates/customTemplateSection.tsx +793 -0
  157. package/src/components/SidePanel/Templates/styles.scss +244 -0
  158. package/src/components/SidePanel/index.tsx +160 -0
  159. package/src/components/TemplateBuilder/index.tsx +585 -0
  160. package/src/components/TemplateBuilder/styles.scss +100 -0
  161. package/src/components/TemplateTypes/index.tsx +96 -0
  162. package/src/components/TemplateTypes/styles.scss +91 -0
  163. package/src/components/TopNavigation/ConfirmNavigateDialog/index.tsx +81 -0
  164. package/src/components/TopNavigation/ConfirmNavigateDialog/styles.scss +123 -0
  165. package/src/components/TopNavigation/DuplicateTemplateModal.tsx +103 -0
  166. package/src/components/TopNavigation/EditTemplateNameModel/index.tsx +71 -0
  167. package/src/components/TopNavigation/EditTemplateNameModel/styles.scss +88 -0
  168. package/src/components/TopNavigation/SaveTemplateModel/index.tsx +201 -0
  169. package/src/components/TopNavigation/SaveTemplateModel/styles.scss +128 -0
  170. package/src/components/TopNavigation/index.tsx +938 -0
  171. package/src/components/TopNavigation/styles.scss +303 -0
  172. package/src/importMeta.d.ts +31 -0
  173. package/src/index.scss +131 -0
  174. package/src/index.tsx +238 -0
  175. package/src/libs/test.ts +7 -0
  176. package/src/redux/actions/action-types.ts +52 -0
  177. package/src/redux/actions/customQRCodeActions.ts +54 -0
  178. package/src/redux/actions/snackbarActions.ts +16 -0
  179. package/src/redux/actions/templateActions.ts +236 -0
  180. package/src/redux/reducers/customFieldReducer.ts +99 -0
  181. package/src/redux/reducers/customQRCodeReducer.ts +58 -0
  182. package/src/redux/reducers/index.ts +15 -0
  183. package/src/redux/reducers/snackbarReducer.ts +40 -0
  184. package/src/redux/reducers/templateReducer.ts +485 -0
  185. package/src/redux/store.ts +18 -0
  186. package/src/styles/colors.scss +61 -0
  187. package/src/test/mocks.js +89 -0
  188. package/src/test/setupJest.js +1 -0
  189. package/src/utils/api.ts +36 -0
  190. package/src/utils/constants.ts +182 -0
  191. package/src/utils/customStyles.ts +45 -0
  192. package/src/utils/fetchWrapper.ts +73 -0
  193. package/src/utils/fonts.json +1597 -0
  194. package/src/utils/helper.ts +205 -0
  195. package/src/utils/local-storage.ts +15 -0
  196. package/src/utils/message.ts +162 -0
  197. package/src/utils/products.ts +186 -0
  198. package/src/utils/template-builder.ts +328 -0
  199. package/src/utils/templateIdentifierArea/biFold.ts +107 -0
  200. package/src/utils/templateIdentifierArea/index.ts +35 -0
  201. package/src/utils/templateIdentifierArea/personal.ts +107 -0
  202. package/src/utils/templateIdentifierArea/postCards.ts +163 -0
  203. package/src/utils/templateIdentifierArea/professional.ts +125 -0
  204. package/src/utils/templateIdentifierArea/snapPack.ts +107 -0
  205. package/src/utils/templateIdentifierArea/triFold.ts +107 -0
  206. package/src/utils/templateRestrictedArea/biFold.ts +329 -0
  207. package/src/utils/templateRestrictedArea/nonWindowProfessional.ts +90 -0
  208. package/src/utils/templateRestrictedArea/personal.ts +90 -0
  209. package/src/utils/templateRestrictedArea/postCard.ts +334 -0
  210. package/src/utils/templateRestrictedArea/postCardJumbo.tsx +408 -0
  211. package/src/utils/templateRestrictedArea/professional.ts +318 -0
  212. package/src/utils/templateRestrictedArea/realPenned.ts +233 -0
  213. package/src/utils/templateRestrictedArea/snapPack.ts +1009 -0
  214. package/src/utils/templateRestrictedArea/triFold.ts +330 -0
  215. package/src/utils/templateSafetyBorders/biFold.ts +91 -0
  216. package/src/utils/templateSafetyBorders/index.ts +43 -0
  217. package/src/utils/templateSafetyBorders/personal.ts +41 -0
  218. package/src/utils/templateSafetyBorders/postCards.ts +259 -0
  219. package/src/utils/templateSafetyBorders/professional.ts +78 -0
  220. package/src/utils/templateSafetyBorders/snapPack.ts +165 -0
  221. package/src/utils/templateSafetyBorders/triFold.ts +114 -0
  222. package/src/utils/templateSafetyBorders/types.d.ts +68 -0
  223. package/src/utils/types.ts +12 -0
  224. package/src/v2Theme.scss +142 -0
  225. package/tsconfig.json +29 -0
  226. package/tsconfig.node.json +12 -0
  227. package/update-version.js +23 -0
  228. package/version.js +1 -0
  229. package/vite.config.ts +8 -0
  230. package/webpack.config.js +80 -0
@@ -0,0 +1,1070 @@
1
+ import React, { useEffect, useState } from 'react';
2
+
3
+ // Import Polotno and third-party libraries
4
+ import QRCode from 'qrcode';
5
+ import { observer } from 'mobx-react-lite';
6
+ import { SectionTab } from 'polotno/side-panel';
7
+ import type { StoreType } from 'polotno/model/store';
8
+
9
+ import { useDispatch, useSelector } from 'react-redux';
10
+ import { AppDispatch, RootState } from '../../../redux/store';
11
+ import { failure, success } from '../../../redux/actions/snackbarActions';
12
+ import {
13
+ setQrUrl,
14
+ setUtmSource,
15
+ setUtmMedium,
16
+ setUtmCampaignName,
17
+ setCustomUtms,
18
+ clearQrFields,
19
+ setIsQR,
20
+ setQrDialog,
21
+ closeQrDialog,
22
+ } from '../../../redux/actions/customQRCodeActions';
23
+ import { uploadFile } from '../../../redux/actions/templateActions';
24
+
25
+ // Utils
26
+ import { MESSAGES } from '../../../utils/message';
27
+ import { validURL } from '../../../utils/helper';
28
+ import {
29
+ DISALLOWED_DOMAINS,
30
+ MERGE_UTM_PARAMS,
31
+ emojiRegex,
32
+ } from '../../../utils/constants';
33
+
34
+ //Components
35
+ import GeneralSelect from '../../../components/GenericUIBlocks/GeneralSelect';
36
+ import QRCodeModal from './V2/QRCodeModal';
37
+ import DialogV2 from '../../../components/GenericUIBlocks/Dialog/V2';
38
+
39
+ // UI Components
40
+ import Input from '../../../components/GenericUIBlocks/Input';
41
+ import Typography from '../../GenericUIBlocks/Typography';
42
+
43
+ // Icons
44
+ import CustomQRIcon from '../../../assets/images/templates/custom-qr-section-icon';
45
+ import QRCodeIcon from '../../../assets/images/templates/qr-code';
46
+
47
+ // @ts-ignore
48
+ import Edit from '../../../assets/images/templates/edit.svg';
49
+ // @ts-ignore
50
+ import Archive from '../../../assets/images/templates/archive.svg';
51
+
52
+ // @ts-ignore
53
+ import ConfirmCloseIcon from '../../../assets/images/modal-icons/confirm-close-icon';
54
+ // @ts-ignore
55
+ import Actions from '../../../assets/images/templates/actions.svg'
56
+
57
+ // styles
58
+ import './styles.scss';
59
+
60
+ interface CustomQRProps {
61
+ store: StoreType;
62
+ allowSenderFields: any;
63
+ allowPropertyFields: any;
64
+ excludedFields: any;
65
+ currentTheme?: string | null | undefined;
66
+ onGetQRCodes?: (payload: any) => Promise<any>;
67
+ onDeleteQRCodes?: (id: string | number) => Promise<void>;
68
+ onUploadQRCode?: (payload: any) => Promise<any>;
69
+ onEditQRCode?: (payload: any) => Promise<any>;
70
+ }
71
+
72
+ const cancelDialogStylesV2 = {
73
+ maxWidth: '567px',
74
+ minHeight: 'auto',
75
+ padding: '40px',
76
+ };
77
+
78
+ // define the new custom section
79
+ const CustomQRCode = {
80
+ name: 'QR-Section',
81
+ Tab: (props: any) => (
82
+ <SectionTab name="QR" {...props} iconSize={20}>
83
+ <CustomQRIcon />
84
+ </SectionTab>
85
+ ),
86
+
87
+ // we need observer to update component automatically on any store changes
88
+ Panel: observer(
89
+ ({
90
+ store,
91
+ allowSenderFields,
92
+ allowPropertyFields,
93
+ excludedFields,
94
+ currentTheme,
95
+ onGetQRCodes,
96
+ onDeleteQRCodes,
97
+ onUploadQRCode,
98
+ onEditQRCode,
99
+ }: CustomQRProps) => {
100
+ // V2 State for API-driven functionality
101
+ const [qrCodes, setQrCodes] = useState<any[]>([]);
102
+ const [isLoading, setIsLoading] = useState(false);
103
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
104
+ const [currentPage, setCurrentPage] = useState(1);
105
+ const [hasMore, setHasMore] = useState(true);
106
+ const [activeDropdown, setActiveDropdown] = useState<
107
+ string | number | null
108
+ >(null);
109
+ const [isEditing, setIsEditing] = useState(false);
110
+ const [selectedQR, setSelectedQR] = useState<any>(null);
111
+ const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
112
+ const [isLoadingDelete, setIsLoadingDelete] = useState(false);
113
+ const [saveQrLoading, setSaveQrLoading] = useState(false);
114
+
115
+ const [isActionsOpen, setIsActionsOpen] = useState<
116
+ string | number | null
117
+ >(null);
118
+ const [actionsDropdownUpward, setActionsDropdownUpward] = useState(false);
119
+
120
+ const qrCodesGridRef = React.useRef<HTMLDivElement>(null);
121
+ const dropdownRefs = React.useRef<Map<string | number, HTMLDivElement>>(
122
+ new Map()
123
+ );
124
+ const actionsRefs = React.useRef<Map<string | number, HTMLDivElement>>(
125
+ new Map()
126
+ );
127
+ const loadingTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
128
+ const isLoadingRef = React.useRef(false);
129
+
130
+ const noteStyles = {
131
+ fontWeight: '400',
132
+ fontSize: '12px',
133
+ color: '#000',
134
+ width: '100%',
135
+ maxWidth: '245px',
136
+ margin: '16px 0 20px',
137
+ };
138
+
139
+ const itemStyle = {
140
+ margin: 0,
141
+ fontSize: '12px',
142
+ fontWeight: '300',
143
+ };
144
+
145
+ const handleDialogChange = (model = '') => {
146
+ if (dialog.open) {
147
+ dispatch(closeQrDialog());
148
+ } else {
149
+ dispatch(setQrDialog(true, model));
150
+ }
151
+ };
152
+
153
+ const handleCloseDeleteDialog = () => {
154
+ setOpenDeleteDialog(false);
155
+ };
156
+
157
+ const handleOpenDeleteDialog = () => {
158
+ setOpenDeleteDialog(true);
159
+ };
160
+
161
+ const handleDialogClose = (model = '') => {
162
+ dispatch(closeQrDialog());
163
+ setIsEditing(false);
164
+ dispatch(setQrUrl(''));
165
+ dispatch(setUtmSource('direct mail'));
166
+ dispatch(setUtmMedium('QR Code'));
167
+ dispatch(setUtmCampaignName(''));
168
+ dispatch(setCustomUtms({}));
169
+ };
170
+
171
+
172
+ const dialog = useSelector((state: RootState) => state.customQRCode.dialog);
173
+ const url = useSelector((state: RootState) => state.customQRCode.url);
174
+ const utmSource = useSelector(
175
+ (state: RootState) => state.customQRCode.utmSource
176
+ );
177
+ const utmMedium = useSelector(
178
+ (state: RootState) => state.customQRCode.utmMedium
179
+ );
180
+ const utmCampaignName = useSelector(
181
+ (state: RootState) => state.customQRCode.utmCampaignName
182
+ );
183
+ const customUtms = useSelector(
184
+ (state: RootState) => state.customQRCode.customUtms
185
+ );
186
+ const isQR = useSelector((state: RootState) => state.customQRCode.isQR);
187
+
188
+ const dispatch: AppDispatch = useDispatch();
189
+
190
+ const defaultFields = useSelector(
191
+ (state: RootState) => state.templates.defaultDynamicFields
192
+ );
193
+
194
+ const defaultSenderFields = useSelector(
195
+ (state: RootState) => state.templates.defaultSenderFields
196
+
197
+ );
198
+
199
+ const customFields = useSelector(
200
+ (state: RootState) => state.customFields.customFields
201
+ );
202
+
203
+ const customFieldsV2 = useSelector(
204
+ (state: RootState) => state.customFields.customFieldsV2
205
+ ) as Record<string, any>;
206
+
207
+ const defaultPropertyFields = useSelector(
208
+ (state: RootState) => state.templates.defaultPropertyFields
209
+ );
210
+
211
+ const excludedLabels = ['utm_c_first_name c_last_name'];
212
+
213
+ let flattenedFieldsV2 = [];
214
+ if (customFieldsV2.length > 0) {
215
+ flattenedFieldsV2 = customFieldsV2?.flatMap(
216
+ (section: { fields: any }) => section.fields
217
+ );
218
+ }
219
+
220
+ const allFields = [
221
+ ...defaultFields,
222
+ ...customFields,
223
+ ...flattenedFieldsV2,
224
+ ...(allowSenderFields ? defaultSenderFields : []),
225
+ ...(allowPropertyFields ? defaultPropertyFields : []),
226
+ ...(allowPropertyFields
227
+ ? [
228
+ {
229
+ value: 'ROS.PROPERTY_OFFER',
230
+ key: '{{ROS.PROPERTY_OFFER}}',
231
+ defaultValue: '$123,456.00',
232
+ },
233
+ ]
234
+ : []),
235
+ ].filter(({ key }) => !excludedFields?.includes(key));
236
+
237
+ const utmFields = allFields
238
+ .map(({ key }) => ({
239
+ label: `utm_${key?.toLowerCase().replaceAll('.', '_').replaceAll(/[{}]/g, '')}`,
240
+ }))
241
+ .filter((utmField) => !excludedLabels.includes(utmField.label));
242
+
243
+ const utms = ['custom_utm_1', 'custom_utm_2', 'custom_utm_3'];
244
+
245
+ const el = store.selectedElements[0];
246
+
247
+ const clearQRFields = () => {
248
+ store.selectElements([]);
249
+ dispatch(clearQrFields());
250
+ };
251
+
252
+ const appendUtmParameters = (utmField: string, defaultValue: string) => {
253
+ let result = '';
254
+ const field = MERGE_UTM_PARAMS.find((field) => field.key === utmField);
255
+ if (field) {
256
+ result = `${field.value}=${defaultValue}`;
257
+ }
258
+ return result;
259
+ };
260
+
261
+ const validateQRCode = () => {
262
+ const validations = [
263
+ { value: utmSource, label: 'UTM Source' },
264
+ { value: utmMedium, label: 'UTM Medium' },
265
+ { value: utmCampaignName, label: 'UTM Campaign' },
266
+ ];
267
+
268
+ for (const { value, label } of validations) {
269
+ if (value.length >= 150) {
270
+ dispatch(failure(`${label} must be less than 150 characters`));
271
+ return false;
272
+ }
273
+ if (emojiRegex.test(value)) {
274
+ dispatch(failure(`Emoji are not allowed in ${label}`));
275
+ return false;
276
+ }
277
+ }
278
+
279
+ return true;
280
+ };
281
+
282
+ const containsDisallowedDomains = (str: string) => {
283
+ return DISALLOWED_DOMAINS.some((substring) => str.includes(substring));
284
+ };
285
+
286
+ // create svg image for QR code for input text
287
+ const getQR = (text: string) => {
288
+ return new Promise<string>((resolve, reject) => {
289
+ QRCode.toDataURL(
290
+ text || 'no-data',
291
+ {
292
+ type: 'image/png',
293
+ margin: 0,
294
+ color: {
295
+ dark: '#000000',
296
+ light: '#0000', // transparent background
297
+ },
298
+ scale: 10, // increase for higher resolution
299
+ },
300
+ (err: any, url: string) => {
301
+ if (err) return reject(err);
302
+ resolve(url); // returns base64 PNG image string
303
+ }
304
+ );
305
+ });
306
+ };
307
+
308
+ // Converts base64 data URL to File
309
+ const base64ToFile = (base64: string, filename: string): File => {
310
+ const arr = base64.split(',');
311
+ const mime = arr[0].match(/:(.*?);/)?.[1] || 'image/png';
312
+ const bstr = atob(arr[1]);
313
+ let n = bstr.length;
314
+ const u8arr = new Uint8Array(n);
315
+ while (n--) {
316
+ u8arr[n] = bstr.charCodeAt(n);
317
+ }
318
+ return new File([u8arr], filename, { type: mime });
319
+ };
320
+
321
+ const createCustomizeURL = (url: string) => {
322
+ let customURL = url;
323
+ let params = [];
324
+ if (utmSource) {
325
+ params.push(`utm_source=${utmSource.replace(/ /g, '_')}`);
326
+ }
327
+ if (utmMedium) {
328
+ params.push(`utm_medium=${utmMedium.replace(/ /g, '_')}`);
329
+ }
330
+ if (utmCampaignName) {
331
+ params.push(`utm_campaign=${utmCampaignName.replace(/ /g, '_')}`);
332
+ }
333
+
334
+ if (customUtms[utms[0]]?.label) {
335
+ const orignalField = `{{${customUtms[utms[0]]?.label.replace('utm_', '').replace('_', '.').toUpperCase()}}}`;
336
+ const defaultValue = allFields.find(
337
+ (field) => field.key === orignalField
338
+ )?.defaultValue;
339
+ const attachUtmKeys = appendUtmParameters(orignalField, defaultValue);
340
+ const mergeUtmParams =
341
+ attachUtmKeys && orignalField == '{{C.PHONE_NUMBER}}'
342
+ ? encodeURIComponent(
343
+ `${customUtms[utms[0]]?.label}=${defaultValue}&${attachUtmKeys}`
344
+ ).replace(/[\s\(\)]/g, '')
345
+ : attachUtmKeys
346
+ ? `${customUtms[utms[0]]?.label}=${defaultValue}&${attachUtmKeys}`
347
+ : `${customUtms[utms[0]]?.label}=${defaultValue}`;
348
+ params.push(mergeUtmParams);
349
+ }
350
+
351
+ if (customUtms[utms[1]]?.label) {
352
+ const orignalField = `{{${customUtms[utms[1]]?.label.replace('utm_', '').replace('_', '.').toUpperCase()}}}`;
353
+ const defaultValue = allFields.find(
354
+ (field) => field.key === orignalField
355
+ )?.defaultValue;
356
+ const attachUtmKeys = appendUtmParameters(orignalField, defaultValue);
357
+ const mergeUtmParams =
358
+ attachUtmKeys && orignalField == '{{C.PHONE_NUMBER}}'
359
+ ? encodeURIComponent(
360
+ `${customUtms[utms[1]]?.label}=${defaultValue}&${attachUtmKeys}`
361
+ ).replace(/[\s\(\)]/g, '')
362
+ : attachUtmKeys
363
+ ? `${customUtms[utms[1]]?.label}=${defaultValue}&${attachUtmKeys}`
364
+ : `${customUtms[utms[1]]?.label}=${defaultValue}`;
365
+ params.push(mergeUtmParams);
366
+ }
367
+
368
+ if (customUtms[utms[2]]?.label) {
369
+ const orignalField = `{{${customUtms[utms[2]]?.label.replace('utm_', '').replace('_', '.').toUpperCase()}}}`;
370
+ const defaultValue = allFields.find(
371
+ (field) => field.key === orignalField
372
+ )?.defaultValue;
373
+ const attachUtmKeys = appendUtmParameters(orignalField, defaultValue);
374
+ const mergeUtmParams =
375
+ attachUtmKeys && orignalField == '{{C.PHONE_NUMBER}}'
376
+ ? encodeURIComponent(
377
+ `${customUtms[utms[2]]?.label}=${defaultValue}&${attachUtmKeys}`
378
+ ).replace(/[\s\(\)]/g, '')
379
+ : attachUtmKeys
380
+ ? `${customUtms[utms[2]]?.label}=${defaultValue}&${attachUtmKeys}`
381
+ : `${customUtms[utms[2]]?.label}=${defaultValue}`;
382
+ params.push(mergeUtmParams);
383
+ }
384
+
385
+ if (params.length > 0) {
386
+ customURL += `/?${params.join('&')}`;
387
+ }
388
+
389
+ return encodeURI(customURL);
390
+ };
391
+
392
+ const addNewQRCode = async () => {
393
+ if (utmCampaignName) {
394
+ if (url) {
395
+ if (validURL(url) && !containsDisallowedDomains(url)) {
396
+ const isValidQR = validateQRCode();
397
+ if (!isValidQR) return false;
398
+
399
+ setSaveQrLoading(true);
400
+ const randomizedId = Math.random().toString(36).substring(2, 7);
401
+ const customQRUrl = createCustomizeURL(url);
402
+
403
+ const src = await getQR(customQRUrl);
404
+ if (currentTheme === 'v2' && onUploadQRCode && onGetQRCodes) {
405
+ const file = base64ToFile(src, `${utmCampaignName}-qr.png`);
406
+ const uploadedFile = await uploadFile(file);
407
+ if (!uploadedFile) {
408
+ setSaveQrLoading(false);
409
+ return dispatch(failure(MESSAGES.TEMPLATE.QR_SECTION.FAILED_QR));
410
+ }
411
+ const payload = {
412
+ name: utmCampaignName,
413
+ qrCodeJson: {
414
+ name: utmCampaignName,
415
+ url: url,
416
+ qrImagePath: uploadedFile,
417
+ utm_source: utmSource,
418
+ utm_medium: utmMedium,
419
+ utm_campaign_name: utmCampaignName,
420
+ custom_utms: customUtms,
421
+ },
422
+ };
423
+
424
+ try {
425
+ await onUploadQRCode(payload);
426
+ // Reset pagination and reload from first page
427
+ setCurrentPage(1);
428
+ setHasMore(true);
429
+ await loadQRCodes(1, false, false);
430
+ handleDialogClose(); // Close modal
431
+ clearQRFields();
432
+ setSaveQrLoading(false);
433
+ } catch (error) {
434
+ setSaveQrLoading(false);
435
+ console.error('Failed to create QR code:', error);
436
+ }
437
+ } else {
438
+ store.activePage.addElement({
439
+ id: `qr-${randomizedId}`,
440
+ type: 'image',
441
+ name: 'qr',
442
+ x: 50,
443
+ y: 50,
444
+ width: 100,
445
+ height: 100,
446
+ blurRadius: 0,
447
+ keepRatio: true,
448
+ src,
449
+ custom: {
450
+ url,
451
+ utm_source: utmSource,
452
+ utm_medium: utmMedium,
453
+ utm_campaign_name: utmCampaignName,
454
+ custom_utms: customUtms,
455
+ },
456
+ });
457
+ clearQRFields();
458
+ }
459
+ setSaveQrLoading(false);
460
+ } else {
461
+ dispatch(failure(MESSAGES.TEMPLATE.QR_SECTION.INVALID_URL));
462
+ }
463
+ } else {
464
+ dispatch(failure(MESSAGES.TEMPLATE.QR_SECTION.EMPTY_QR));
465
+ }
466
+ } else {
467
+ dispatch(failure(MESSAGES.TEMPLATE.QR_SECTION.EMPTY_CAMPAIGN));
468
+ }
469
+ };
470
+
471
+ // V2 API Functions
472
+ const loadQRCodes = React.useCallback(
473
+ async (page: number = 1, append: boolean = false, loading = true) => {
474
+ if (!onGetQRCodes) return;
475
+
476
+ if (isLoadingRef.current) return;
477
+ isLoadingRef.current = true;
478
+
479
+ if (page === 1 && loading) {
480
+ setIsLoading(true);
481
+ } else {
482
+ setIsLoadingMore(true);
483
+ }
484
+
485
+ try {
486
+ const payload = {
487
+ page,
488
+ pageSize: 15,
489
+ };
490
+
491
+ const qrCodesData = await onGetQRCodes(payload);
492
+
493
+ if (qrCodesData && Array.isArray(qrCodesData.rows)) {
494
+ const normalized = qrCodesData.rows.map((qr: any) => ({
495
+ id: qr.id,
496
+ name: qr?.qrCodeJson?.name || qr?.qrCodeJson?.utm_campaign_name || '',
497
+ url: qr.qrCodeJson.url,
498
+ qrImagePath: qr.qrCodeJson.qrImagePath,
499
+ status: qr.status,
500
+ utm_source: qr.qrCodeJson.utm_source,
501
+ utm_medium: qr.qrCodeJson.utm_medium,
502
+ utm_campaign_name: qr.qrCodeJson.utm_campaign_name,
503
+ custom_utms: qr.qrCodeJson.custom_utms,
504
+ }));
505
+
506
+ if (append && page > 1) {
507
+ setQrCodes((prevQrCodes) => {
508
+ const existingIds = new Set(
509
+ prevQrCodes.map((qr: any) => qr.id)
510
+ );
511
+ const newQrCodes = normalized.filter(
512
+ (qr: any) => !existingIds.has(qr.id)
513
+ );
514
+ return [...prevQrCodes, ...newQrCodes];
515
+ });
516
+ } else {
517
+ setQrCodes(normalized);
518
+ }
519
+
520
+ const hasMorePages =
521
+ qrCodesData.currentPage < qrCodesData.lastPage;
522
+ setHasMore(hasMorePages);
523
+ setCurrentPage(qrCodesData.currentPage);
524
+ } else {
525
+ if (!append) {
526
+ setQrCodes([]);
527
+ }
528
+ setHasMore(false);
529
+ }
530
+ } catch (error) {
531
+ console.error('Failed to load QR codes:', error);
532
+ } finally {
533
+ setIsLoading(false);
534
+ setIsLoadingMore(false);
535
+ isLoadingRef.current = false;
536
+ }
537
+ },
538
+ [onGetQRCodes]
539
+ );
540
+
541
+ const loadMoreQRCodes = React.useCallback(async () => {
542
+ if (!hasMore || isLoadingMore || isLoading) return;
543
+ await loadQRCodes(currentPage + 1, true);
544
+ }, [hasMore, isLoadingMore, isLoading, currentPage, loadQRCodes]);
545
+
546
+ const handleScroll = React.useCallback(() => {
547
+ if (!qrCodesGridRef.current || !hasMore || isLoadingMore || isLoading)
548
+ return;
549
+
550
+ const { scrollTop, scrollHeight, clientHeight } = qrCodesGridRef.current;
551
+ const scrollPercentage = (scrollTop + clientHeight) / scrollHeight;
552
+
553
+ if (scrollPercentage > 0.75) {
554
+ if (loadingTimeoutRef.current) {
555
+ clearTimeout(loadingTimeoutRef.current);
556
+ }
557
+
558
+ loadingTimeoutRef.current = setTimeout(() => {
559
+ loadMoreQRCodes();
560
+ }, 300);
561
+ }
562
+ }, [hasMore, isLoadingMore, isLoading, loadMoreQRCodes]);
563
+
564
+ const handleQRCodeSelect = async (qrCode: any) => {
565
+ let customQRUrl = '';
566
+ let src: any = '';
567
+ let customData = {};
568
+
569
+ if (currentTheme === 'v2') {
570
+ customQRUrl = createCustomizeURL(qrCode.url);
571
+ src = qrCode.qrImagePath;
572
+ customData = {
573
+ url: qrCode.url,
574
+ qrCodeInstanceId: qrCode.id,
575
+ qrImagePath: qrCode.qrImagePath,
576
+ utm_source: qrCode.utm_source,
577
+ utm_medium: qrCode.utm_medium,
578
+ utm_campaign_name: qrCode.utm_campaign_name,
579
+ custom_utms: qrCode.custom_utms,
580
+ }
581
+ } else {
582
+ customQRUrl = createCustomizeURL(qrCode.url);
583
+ src = await getQR(customQRUrl);
584
+ customData = {
585
+ url: qrCode.url,
586
+ utm_source: qrCode.utmSource,
587
+ utm_medium: qrCode.utmMedium,
588
+ utm_campaign_name: qrCode.utmCampaign,
589
+ custom_utms: qrCode.customUtms,
590
+ }
591
+ }
592
+
593
+ const randomizedId = Math.random().toString(36).substring(2, 7);
594
+ store.activePage.addElement({
595
+ id: `qr-${randomizedId}`,
596
+ type: 'image',
597
+ name: 'qr',
598
+ x: 50,
599
+ y: 50,
600
+ width: 100,
601
+ height: 100,
602
+ blurRadius: 0,
603
+ keepRatio: true,
604
+ src,
605
+ custom: customData,
606
+ });
607
+ };
608
+
609
+ const handleDeleteQRCode = async (qrCodeId: string | number) => {
610
+ if (!onDeleteQRCodes) return;
611
+ setIsLoadingDelete(true);
612
+ try {
613
+ await onDeleteQRCodes(qrCodeId);
614
+ setQrCodes((prev) => prev.filter((qr) => qr.id !== qrCodeId));
615
+ setActiveDropdown(null);
616
+ setIsActionsOpen(null);
617
+ } catch (error) {
618
+ console.error('Failed to delete QR code:', error);
619
+ }
620
+ finally {
621
+ setIsLoadingDelete(false);
622
+ }
623
+ };
624
+
625
+ const handleEditQRCode = async (qrCode: any) => {
626
+ setIsEditing(true);
627
+ setSelectedQR(qrCode);
628
+ dispatch(setQrUrl(qrCode.url));
629
+ dispatch(setUtmSource(qrCode.utmSource || 'direct mail'));
630
+ dispatch(setUtmMedium(qrCode.utmMedium || 'QR Code'));
631
+ dispatch(setUtmCampaignName(qrCode.utm_campaign_name || ''));
632
+ dispatch(setCustomUtms(qrCode.custom_utms || {}));
633
+
634
+ handleDialogChange('qr-modal');
635
+ setIsActionsOpen(null);
636
+ };
637
+
638
+ const toggleActions = (
639
+ qrCodeId: string | number,
640
+ event: React.MouseEvent
641
+ ) => {
642
+ event.stopPropagation();
643
+ const isOpening = isActionsOpen !== qrCodeId;
644
+ if (isOpening) {
645
+ const triggerEl = actionsRefs.current.get(qrCodeId);
646
+ const scrollContainer = qrCodesGridRef.current;
647
+ const dropdownHeight = 80;
648
+ if (triggerEl && scrollContainer) {
649
+ const triggerRect = triggerEl.getBoundingClientRect();
650
+ const containerRect = scrollContainer.getBoundingClientRect();
651
+ const spaceBelow = containerRect.bottom - triggerRect.bottom;
652
+ setActionsDropdownUpward(spaceBelow < dropdownHeight);
653
+ } else {
654
+ setActionsDropdownUpward(false);
655
+ }
656
+ }
657
+ setIsActionsOpen(isActionsOpen === qrCodeId ? null : qrCodeId);
658
+ };
659
+
660
+ // if selection is changed we need to update input value
661
+ const updateQRCode = async () => {
662
+ if (url) {
663
+ if (validURL(url) && !containsDisallowedDomains(url)) {
664
+ const isValidQR = validateQRCode();
665
+ if (!isValidQR) return false;
666
+
667
+ setSaveQrLoading(true);
668
+ const customQRUrl = createCustomizeURL(url);
669
+ const src = await getQR(customQRUrl);
670
+
671
+ if (currentTheme === 'v2' && onEditQRCode && onGetQRCodes) {
672
+ const file = base64ToFile(src, `${utmCampaignName}-qr.png`);
673
+ const uploadedFile = await uploadFile(file);
674
+ const payload = {
675
+ name: utmCampaignName,
676
+ id: selectedQR?.id,
677
+ qrCodeJson: {
678
+ name: utmCampaignName,
679
+ url: url,
680
+ qrImagePath: uploadedFile,
681
+ utm_source: utmSource,
682
+ utm_medium: utmMedium,
683
+ utm_campaign_name: utmCampaignName,
684
+ custom_utms: customUtms,
685
+ },
686
+ };
687
+
688
+ try {
689
+ await onEditQRCode(payload);
690
+ // Reset pagination and reload from first page
691
+ setCurrentPage(1);
692
+ setHasMore(true);
693
+ await loadQRCodes(1, false, false);
694
+ handleDialogClose(); // Close modal
695
+ clearQRFields();
696
+ setSaveQrLoading(false);
697
+ } catch (error) {
698
+ setSaveQrLoading(false);
699
+ console.error('Failed to create QR code:', error);
700
+ }
701
+ } else if (el?.name === 'qr' && url) {
702
+ el.set({
703
+ src,
704
+ custom: {
705
+ url,
706
+ utm_source: utmSource,
707
+ utm_medium: utmMedium,
708
+ utm_campaign_name: utmCampaignName,
709
+ custom_utms: customUtms,
710
+ },
711
+ });
712
+ clearQRFields();
713
+ setSaveQrLoading(false);
714
+ }
715
+ } else {
716
+ dispatch(failure(MESSAGES.TEMPLATE.QR_SECTION.INVALID_URL));
717
+ }
718
+ } else {
719
+ dispatch(failure(MESSAGES.TEMPLATE.QR_SECTION.EMPTY_QR));
720
+ }
721
+ };
722
+
723
+ // Handler to update dropdown values
724
+ const handleSelect = (utmKey: string, value: any) => {
725
+ const updatedUtms = { ...customUtms };
726
+ if (value === null) {
727
+ delete updatedUtms[utmKey];
728
+ } else {
729
+ updatedUtms[utmKey] = value;
730
+ }
731
+ dispatch(setCustomUtms(updatedUtms));
732
+ };
733
+
734
+ // if selection is changed we need to update input value
735
+ useEffect(() => {
736
+ if (el?.name === 'qr' && currentTheme !== 'v2') {
737
+ dispatch(setIsQR(el?.name === 'qr'));
738
+ dispatch(setQrUrl(el?.custom?.url || el?.custom?.value || ''));
739
+ dispatch(setUtmSource(el?.custom?.utm_source || 'direct mail'));
740
+ dispatch(setUtmMedium(el?.custom?.utm_medium || 'QR Code'));
741
+ dispatch(setUtmCampaignName(el?.custom?.utm_campaign_name || ''));
742
+ if (Object.values(el?.custom?.custom_utms || {}).length) {
743
+ dispatch(setCustomUtms(el?.custom?.custom_utms));
744
+ } else {
745
+ dispatch(setCustomUtms({}));
746
+ }
747
+ } else if (isQR && el?.name !== 'qr') {
748
+ dispatch(clearQrFields());
749
+ }
750
+ }, [isQR, el]);
751
+
752
+ // Handle click outside to close actions menu
753
+ useEffect(() => {
754
+ const handleClickOutside = (event: MouseEvent) => {
755
+ // Close actions if clicking outside the currently open actions container
756
+ if (isActionsOpen !== null) {
757
+ const actionsElement = actionsRefs.current.get(isActionsOpen);
758
+ if (
759
+ actionsElement &&
760
+ !actionsElement.contains(event.target as Node)
761
+ ) {
762
+ setIsActionsOpen(null);
763
+ }
764
+ }
765
+
766
+ // V2: Handle dropdowns
767
+ if (activeDropdown) {
768
+ const dropdownElement = dropdownRefs.current.get(activeDropdown);
769
+ if (
770
+ dropdownElement &&
771
+ !dropdownElement.contains(event.target as Node)
772
+ ) {
773
+ setActiveDropdown(null);
774
+ }
775
+ }
776
+ };
777
+
778
+ if (isActionsOpen !== null || activeDropdown !== null) {
779
+ document.addEventListener('mousedown', handleClickOutside);
780
+ }
781
+
782
+ return () => {
783
+ document.removeEventListener('mousedown', handleClickOutside);
784
+ };
785
+ }, [isActionsOpen, activeDropdown]);
786
+
787
+ // V2: Load QR codes on mount if callbacks are provided
788
+ useEffect(() => {
789
+ if (onGetQRCodes) {
790
+ loadQRCodes(1, false);
791
+ }
792
+ }, [onGetQRCodes, loadQRCodes]);
793
+
794
+ // V2: Setup scroll event listener on the list container
795
+ useEffect(() => {
796
+ const listElement = qrCodesGridRef.current;
797
+ if (listElement && onGetQRCodes) {
798
+ listElement.addEventListener('scroll', handleScroll);
799
+ return () => {
800
+ listElement.removeEventListener('scroll', handleScroll);
801
+ if (loadingTimeoutRef.current) {
802
+ clearTimeout(loadingTimeoutRef.current);
803
+ }
804
+ };
805
+ }
806
+ }, [
807
+ hasMore,
808
+ isLoadingMore,
809
+ isLoading,
810
+ currentPage,
811
+ handleScroll,
812
+ onGetQRCodes,
813
+ ]);
814
+
815
+ return (
816
+ <>
817
+ {currentTheme === 'v2' ? (
818
+ <>
819
+ <div
820
+ className="qr-section-v2-container"
821
+ style={{
822
+ padding: '14px',
823
+ }}
824
+ >
825
+ <div
826
+ className="qr-code-wrapper qr-code-wrapper-sticky"
827
+ onClick={() => handleDialogChange('qr-modal')}
828
+ >
829
+ <QRCodeIcon />
830
+ <button className="qr-submit-btn-new">Create QR-Code</button>
831
+ </div>
832
+ <Typography variant="h6" style={noteStyles}>
833
+ {MESSAGES.TEMPLATE.QR_SECTION.QR_NOTE}
834
+ </Typography>
835
+
836
+ {/* V2 QR Codes List when callbacks are provided */}
837
+ {onGetQRCodes && (
838
+ <div
839
+ className="qr-codes-list-scroll"
840
+ ref={qrCodesGridRef}
841
+ >
842
+ <div className="qr-codes-grid">
843
+ {isLoading ? (
844
+ <div className="loading-state">Loading QR codes...</div>
845
+ ) : qrCodes.length === 0 ? (
846
+ <div className="empty-state">No QR codes created yet</div>
847
+ ) : (
848
+ <>
849
+ {qrCodes.map((qrCode) => (
850
+ <div key={qrCode.id} className="qr-card" onClick={() => handleQRCodeSelect(qrCode)}>
851
+ <img
852
+ src={qrCode.qrImagePath}
853
+ alt="code"
854
+ style={{ width: '30px', height: '30px' }}
855
+ />
856
+ <div
857
+ className="qr-card-content"
858
+ >
859
+ <Typography
860
+ style={{
861
+ fontWeight: '600',
862
+ fontSize: '12px',
863
+ color: '#545454',
864
+ margin: 0,
865
+ }}
866
+ >
867
+ {qrCode.name}
868
+ </Typography>
869
+ <Typography
870
+ style={{
871
+ fontWeight: '400',
872
+ fontSize: '12px',
873
+ color: '#54545499',
874
+ margin: 0,
875
+ }}
876
+ >
877
+ {qrCode.status}
878
+ </Typography>
879
+ </div>
880
+ {(onEditQRCode || onDeleteQRCodes) && <div
881
+ className="actions-container"
882
+ ref={(el) => {
883
+ if (el) actionsRefs.current.set(qrCode.id, el);
884
+ }}
885
+ >
886
+ <div
887
+ className="actions"
888
+ onClick={(e) => { toggleActions(qrCode.id, e); e.stopPropagation(); }}
889
+ >
890
+ <img src={Actions} alt='actions' />
891
+ </div>
892
+ {/* Actions */}
893
+ {isActionsOpen === qrCode.id && (
894
+ <div
895
+ className={`actions-wrapper${actionsDropdownUpward ? ' actions-wrapper-up' : ''}`}
896
+ >
897
+ {onEditQRCode && (
898
+ <div
899
+ className="action-item"
900
+ onClick={(e) => {
901
+ e.stopPropagation();
902
+ handleEditQRCode(qrCode);
903
+ }}
904
+ >
905
+ <img src={Edit} alt="edit" />
906
+ <Typography style={itemStyle}>
907
+ Edit
908
+ </Typography>
909
+ </div>
910
+ )}
911
+ {onDeleteQRCodes && (
912
+ <div
913
+ className="action-item"
914
+ onClick={(e) => {
915
+ e.stopPropagation();
916
+ handleOpenDeleteDialog();
917
+ setSelectedQR(qrCode.id);
918
+ }}
919
+ >
920
+ <img src={Archive} alt="Archive" />
921
+ <Typography style={itemStyle}>
922
+ Archive
923
+ </Typography>
924
+ </div>
925
+ )}
926
+ </div>
927
+ )}
928
+ </div>}
929
+ </div>
930
+ ))}
931
+ {/* Loading indicator for pagination */}
932
+ {isLoadingMore && (
933
+ <div className="loading-more">
934
+ <div className="loading-state">
935
+ Loading more QR codes...
936
+ </div>
937
+ </div>
938
+ )}
939
+ </>
940
+ )}
941
+ </div>
942
+ </div>
943
+ )}
944
+ </div>
945
+ {dialog.open && (
946
+ <QRCodeModal
947
+ show={dialog.open}
948
+ utms={utms}
949
+ utmFields={utmFields}
950
+ isEditing={isEditing}
951
+ handleSelect={handleSelect}
952
+ handleDialogChange={handleDialogChange}
953
+ handleClose={handleDialogClose}
954
+ setUtmCampaignName={setUtmCampaignName}
955
+ setQrUrl={setQrUrl}
956
+ setUtmSource={setUtmSource}
957
+ setUtmMedium={setUtmMedium}
958
+ setCustomUtms={setCustomUtms}
959
+ addNewQRCode={isEditing ? updateQRCode : addNewQRCode}
960
+ loading={saveQrLoading}
961
+ />
962
+ )}
963
+ {openDeleteDialog && (
964
+ <DialogV2
965
+ icon={<ConfirmCloseIcon fill='var(--primary-color)' />}
966
+ customStyles={cancelDialogStylesV2}
967
+ open={openDeleteDialog}
968
+ handleClose={handleCloseDeleteDialog}
969
+ title='Delete QR Code'
970
+ subHeading=''
971
+ description='Are you sure you want to delete this QR code?'
972
+ onSubmit={() => {
973
+ handleDeleteQRCode(selectedQR);
974
+ handleCloseDeleteDialog();
975
+ }}
976
+ onCancel={handleCloseDeleteDialog}
977
+ cancelText='No'
978
+ submitText='Yes'
979
+ isGallery={false}
980
+ loading={isLoadingDelete}
981
+ />)}
982
+ </>
983
+ ) : (
984
+ <>
985
+ <button
986
+ className="qr-submit-btn"
987
+ onClick={isQR ? updateQRCode : addNewQRCode}
988
+ >
989
+ {isQR
990
+ ? MESSAGES.TEMPLATE.QR_SECTION.UPDATE_BUTTON
991
+ : MESSAGES.TEMPLATE.QR_SECTION.SUBMIT_BUTTON}
992
+ </button>
993
+ <div className="qr-input-wrapper">
994
+ <label>QR URL*:</label>
995
+ <Input
996
+ type="text"
997
+ onChange={(e) => {
998
+ dispatch(setQrUrl(e.target.value));
999
+ }}
1000
+ placeholder={MESSAGES.TEMPLATE.QR_SECTION.QR_PLACEHOLDER}
1001
+ value={url}
1002
+ qrField={true}
1003
+ />
1004
+ </div>
1005
+ <div className="qr-input-wrapper">
1006
+ <label>UTM Source:</label>
1007
+ <Input
1008
+ type="text"
1009
+ onChange={(e) => {
1010
+ dispatch(setUtmSource(e.target.value));
1011
+ }}
1012
+ placeholder={'Enter UTM Source'}
1013
+ value={utmSource}
1014
+ qrField={true}
1015
+ />
1016
+ </div>
1017
+ <div className="qr-input-wrapper">
1018
+ <label>UTM Medium:</label>
1019
+ <Input
1020
+ type="text"
1021
+ onChange={(e) => {
1022
+ dispatch(setUtmMedium(e.target.value));
1023
+ }}
1024
+ placeholder={'Enter UTM Medium'}
1025
+ value={utmMedium}
1026
+ qrField={true}
1027
+ />
1028
+ </div>
1029
+ <div className="qr-input-wrapper">
1030
+ <label>UTM Campaign Name*:</label>
1031
+ <Input
1032
+ type="text"
1033
+ onChange={(e) => {
1034
+ dispatch(setUtmCampaignName(e.target.value));
1035
+ }}
1036
+ placeholder={'Enter UTM Campaign Name'}
1037
+ value={utmCampaignName}
1038
+ qrField={true}
1039
+ />
1040
+ </div>
1041
+ {utms?.map((utm, idx) => {
1042
+ return (
1043
+ <div className="qr-input-wrapper" key={idx}>
1044
+ <label>{utm.toUpperCase().replace(/\_/g, ' ')}:</label>
1045
+ <GeneralSelect
1046
+ placeholder={`Search / Select Custom UTM ${idx + 1}`}
1047
+ options={utmFields as any}
1048
+ setSelectedValue={(value: any) =>
1049
+ handleSelect(utm, value)
1050
+ }
1051
+ selectedValue={customUtms[utm] || (null as any)}
1052
+ builderSelect={true}
1053
+ clearField={true}
1054
+ search={true}
1055
+ qrField={true}
1056
+ isError={false}
1057
+ gallerySelect={false}
1058
+ />
1059
+ </div>
1060
+ );
1061
+ })}
1062
+ </>
1063
+ )}
1064
+ </>
1065
+ );
1066
+ }
1067
+ ),
1068
+ };
1069
+
1070
+ export default CustomQRCode;