@capillarytech/creatives-library 8.0.235 → 8.0.236-alpha.1

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 (86) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/config/app.js +0 -1
  4. package/constants/unified.js +1 -1
  5. package/initialReducer.js +2 -0
  6. package/package.json +1 -1
  7. package/services/api.js +5 -2
  8. package/services/tests/api.test.js +18 -0
  9. package/utils/common.js +1 -2
  10. package/utils/commonUtils.js +14 -1
  11. package/utils/transformTemplateConfig.js +0 -10
  12. package/v2Components/CapDeviceContent/index.js +61 -56
  13. package/v2Components/CapTagList/index.js +4 -0
  14. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  15. package/v2Components/HtmlEditor/HTMLEditor.js +165 -80
  16. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +532 -0
  17. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +17 -12
  18. package/v2Components/HtmlEditor/_htmlEditor.scss +0 -4
  19. package/v2Components/HtmlEditor/_index.lazy.scss +0 -1
  20. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +0 -98
  21. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +125 -148
  22. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +1 -0
  23. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  24. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  25. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  26. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  27. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  28. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  29. package/v2Components/HtmlEditor/constants.js +29 -20
  30. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +158 -17
  31. package/v2Components/HtmlEditor/hooks/useInAppContent.js +53 -143
  32. package/v2Components/HtmlEditor/index.js +1 -1
  33. package/v2Components/HtmlEditor/messages.js +85 -85
  34. package/v2Components/MobilePushPreviewV2/index.js +32 -7
  35. package/v2Components/TemplatePreview/_templatePreview.scss +31 -21
  36. package/v2Components/TemplatePreview/index.js +47 -32
  37. package/v2Components/TemplatePreview/messages.js +4 -0
  38. package/v2Containers/BeeEditor/index.js +82 -80
  39. package/v2Containers/BeePopupEditor/constants.js +10 -0
  40. package/v2Containers/BeePopupEditor/index.js +180 -0
  41. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  42. package/v2Containers/CreativesContainer/SlideBoxContent.js +69 -34
  43. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
  44. package/v2Containers/CreativesContainer/constants.js +1 -0
  45. package/v2Containers/CreativesContainer/index.js +65 -13
  46. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +4 -12
  47. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -0
  48. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  49. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  50. package/v2Containers/InApp/actions.js +7 -0
  51. package/v2Containers/InApp/constants.js +18 -4
  52. package/v2Containers/InApp/index.js +642 -355
  53. package/v2Containers/InApp/index.scss +4 -3
  54. package/v2Containers/InApp/messages.js +7 -3
  55. package/v2Containers/InApp/reducer.js +21 -3
  56. package/v2Containers/InApp/sagas.js +29 -9
  57. package/v2Containers/InApp/selectors.js +25 -5
  58. package/v2Containers/InApp/tests/index.test.js +154 -50
  59. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  60. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  61. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  62. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +162 -0
  63. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  64. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +9 -0
  65. package/v2Containers/InAppWrapper/constants.js +16 -0
  66. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  67. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  68. package/v2Containers/InAppWrapper/index.js +148 -0
  69. package/v2Containers/InAppWrapper/messages.js +49 -0
  70. package/v2Containers/InappAdvance/index.js +1006 -0
  71. package/v2Containers/InappAdvance/index.scss +10 -0
  72. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  73. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  74. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  75. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
  76. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
  77. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
  78. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
  79. package/v2Containers/TagList/index.js +65 -1
  80. package/v2Containers/Templates/_templates.scss +49 -1
  81. package/v2Containers/Templates/index.js +93 -5
  82. package/v2Containers/Templates/messages.js +4 -0
  83. package/v2Containers/Templates/reducer.js +20 -7
  84. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +8 -88
  85. package/v2Containers/Templates/tests/reducer.test.js +125 -0
  86. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +35 -0
@@ -76,7 +76,7 @@ describe('useInAppContent', () => {
76
76
 
77
77
  it('initializes with custom content for Android', () => {
78
78
  const customContent = {
79
- [DEVICE_TYPES.ANDROID]: '<p>Custom Android</p>'
79
+ [DEVICE_TYPES.ANDROID]: '<p>Custom Android</p>',
80
80
  };
81
81
 
82
82
  render(<TestComponent initialContent={customContent} />);
@@ -86,7 +86,7 @@ describe('useInAppContent', () => {
86
86
 
87
87
  it('initializes with custom content for iOS', () => {
88
88
  const customContent = {
89
- [DEVICE_TYPES.IOS]: '<p>Custom iOS</p>'
89
+ [DEVICE_TYPES.IOS]: '<p>Custom iOS</p>',
90
90
  };
91
91
 
92
92
  render(<TestComponent initialContent={customContent} />);
@@ -97,7 +97,7 @@ describe('useInAppContent', () => {
97
97
  it('initializes with custom content for both devices', () => {
98
98
  const customContent = {
99
99
  [DEVICE_TYPES.ANDROID]: '<p>Android</p>',
100
- [DEVICE_TYPES.IOS]: '<p>iOS</p>'
100
+ [DEVICE_TYPES.IOS]: '<p>iOS</p>',
101
101
  };
102
102
 
103
103
  render(<TestComponent initialContent={customContent} />);
@@ -112,10 +112,10 @@ describe('useInAppContent', () => {
112
112
  expect(screen.getByTestId('active-device')).toHaveTextContent(DEVICE_TYPES.ANDROID);
113
113
  });
114
114
 
115
- it('has content by default', () => {
115
+ it('has no content by default (empty strings)', () => {
116
116
  render(<TestComponent />);
117
117
 
118
- expect(screen.getByTestId('has-content')).toHaveTextContent('true');
118
+ expect(screen.getByTestId('has-content')).toHaveTextContent('false');
119
119
  });
120
120
  });
121
121
 
@@ -152,7 +152,7 @@ describe('useInAppContent', () => {
152
152
  it('updates current content when switching devices', () => {
153
153
  const customContent = {
154
154
  [DEVICE_TYPES.ANDROID]: '<p>Android Content</p>',
155
- [DEVICE_TYPES.IOS]: '<p>iOS Content</p>'
155
+ [DEVICE_TYPES.IOS]: '<p>iOS Content</p>',
156
156
  };
157
157
 
158
158
  render(<TestComponent initialContent={customContent} />);
@@ -220,7 +220,7 @@ describe('useInAppContent', () => {
220
220
  it('updates only current device when sync is disabled', () => {
221
221
  const customContent = {
222
222
  [DEVICE_TYPES.ANDROID]: '<p>Android</p>',
223
- [DEVICE_TYPES.IOS]: '<p>iOS</p>'
223
+ [DEVICE_TYPES.IOS]: '<p>iOS</p>',
224
224
  };
225
225
 
226
226
  let inAppState;
@@ -254,7 +254,7 @@ describe('useInAppContent', () => {
254
254
  expect(mockOnChange).toHaveBeenCalled();
255
255
  expect(mockOnChange).toHaveBeenCalledWith(
256
256
  expect.objectContaining({
257
- [DEVICE_TYPES.ANDROID]: '<p>New</p>'
257
+ [DEVICE_TYPES.ANDROID]: '<p>New</p>',
258
258
  }),
259
259
  DEVICE_TYPES.ANDROID
260
260
  );
@@ -294,7 +294,7 @@ describe('useInAppContent', () => {
294
294
  it('syncs content to both devices when sync is enabled', () => {
295
295
  const customContent = {
296
296
  [DEVICE_TYPES.ANDROID]: '<p>Android</p>',
297
- [DEVICE_TYPES.IOS]: '<p>iOS</p>'
297
+ [DEVICE_TYPES.IOS]: '<p>iOS</p>',
298
298
  };
299
299
 
300
300
  render(<TestComponent initialContent={customContent} />);
@@ -457,7 +457,7 @@ describe('useInAppContent', () => {
457
457
  describe('Content Queries', () => {
458
458
  it('reports hasContent as true for non-empty content', () => {
459
459
  const customContent = {
460
- [DEVICE_TYPES.ANDROID]: '<p>Content</p>'
460
+ [DEVICE_TYPES.ANDROID]: '<p>Content</p>',
461
461
  };
462
462
 
463
463
  render(<TestComponent initialContent={customContent} />);
@@ -468,7 +468,7 @@ describe('useInAppContent', () => {
468
468
  it('reports hasContent as false for empty content', () => {
469
469
  const customContent = {
470
470
  [DEVICE_TYPES.ANDROID]: '',
471
- [DEVICE_TYPES.IOS]: ''
471
+ [DEVICE_TYPES.IOS]: '',
472
472
  };
473
473
 
474
474
  let inAppState;
@@ -487,7 +487,7 @@ describe('useInAppContent', () => {
487
487
 
488
488
  it('calculates content size correctly', () => {
489
489
  const customContent = {
490
- [DEVICE_TYPES.ANDROID]: '12345'
490
+ [DEVICE_TYPES.ANDROID]: '12345',
491
491
  };
492
492
 
493
493
  render(<TestComponent initialContent={customContent} />);
@@ -498,7 +498,7 @@ describe('useInAppContent', () => {
498
498
  it('returns 0 size for empty content', () => {
499
499
  const customContent = {
500
500
  [DEVICE_TYPES.ANDROID]: '',
501
- [DEVICE_TYPES.IOS]: ''
501
+ [DEVICE_TYPES.IOS]: '',
502
502
  };
503
503
 
504
504
  let inAppState;
@@ -518,7 +518,7 @@ describe('useInAppContent', () => {
518
518
  it('getDeviceContent returns content for specific device', () => {
519
519
  const customContent = {
520
520
  [DEVICE_TYPES.ANDROID]: '<p>Android</p>',
521
- [DEVICE_TYPES.IOS]: '<p>iOS</p>'
521
+ [DEVICE_TYPES.IOS]: '<p>iOS</p>',
522
522
  };
523
523
 
524
524
  let inAppState;
@@ -655,7 +655,7 @@ describe('useInAppContent', () => {
655
655
  it('handles very large content', () => {
656
656
  const largeContent = 'a'.repeat(100000);
657
657
  const customContent = {
658
- [DEVICE_TYPES.ANDROID]: largeContent
658
+ [DEVICE_TYPES.ANDROID]: largeContent,
659
659
  };
660
660
 
661
661
  let inAppState;
@@ -698,7 +698,7 @@ describe('useInAppContent', () => {
698
698
  it('switches device and updates content', () => {
699
699
  const customContent = {
700
700
  [DEVICE_TYPES.ANDROID]: '<p>Android</p>',
701
- [DEVICE_TYPES.IOS]: '<p>iOS</p>'
701
+ [DEVICE_TYPES.IOS]: '<p>iOS</p>',
702
702
  };
703
703
 
704
704
  let inAppState;
@@ -781,5 +781,146 @@ describe('useInAppContent', () => {
781
781
  expect(mockOnSave).toHaveBeenCalledTimes(1);
782
782
  });
783
783
  });
784
- });
785
784
 
785
+ describe('setDeviceContent Coverage', () => {
786
+ it('sets content for specific device when sync is disabled', () => {
787
+ let inAppState;
788
+ render(<TestComponent onStateChange={(state) => { inAppState = state; }} />);
789
+
790
+ act(() => {
791
+ inAppState.setDeviceContent(DEVICE_TYPES.ANDROID, '<p>Android Only</p>');
792
+ });
793
+
794
+ expect(screen.getByTestId('android-content')).toHaveTextContent('<p>Android Only</p>');
795
+ expect(screen.getByTestId('ios-content')).toHaveTextContent('');
796
+ });
797
+
798
+ it('sets content for both devices when sync is enabled', () => {
799
+ let inAppState;
800
+ render(<TestComponent onStateChange={(state) => { inAppState = state; }} />);
801
+
802
+ // Enable sync first
803
+ act(() => {
804
+ inAppState.toggleContentSync(true);
805
+ });
806
+
807
+ // Then set content
808
+ act(() => {
809
+ inAppState.setDeviceContent(DEVICE_TYPES.ANDROID, '<p>Synced Content</p>');
810
+ });
811
+
812
+ expect(screen.getByTestId('android-content')).toHaveTextContent('<p>Synced Content</p>');
813
+ expect(screen.getByTestId('ios-content')).toHaveTextContent('<p>Synced Content</p>');
814
+ });
815
+
816
+ it('warns when setting content for invalid device', () => {
817
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
818
+ let inAppState;
819
+ render(<TestComponent onStateChange={(state) => { inAppState = state; }} />);
820
+
821
+ act(() => {
822
+ inAppState.setDeviceContent('InvalidDevice', '<p>Content</p>');
823
+ });
824
+
825
+ expect(consoleSpy).toHaveBeenCalledWith(
826
+ expect.stringContaining('Invalid device type')
827
+ );
828
+
829
+ consoleSpy.mockRestore();
830
+ });
831
+
832
+ it('warns when setting non-string content', () => {
833
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
834
+ let inAppState;
835
+ render(<TestComponent onStateChange={(state) => { inAppState = state; }} />);
836
+
837
+ act(() => {
838
+ inAppState.setDeviceContent(DEVICE_TYPES.ANDROID, 123);
839
+ });
840
+
841
+ expect(consoleSpy).toHaveBeenCalledWith(
842
+ expect.stringContaining('content must be a string')
843
+ );
844
+
845
+ consoleSpy.mockRestore();
846
+ });
847
+ });
848
+
849
+ describe('Edge Cases Coverage', () => {
850
+ it('handles updateContent with non-string input', () => {
851
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
852
+ let inAppState;
853
+ render(<TestComponent onStateChange={(state) => { inAppState = state; }} />);
854
+
855
+ act(() => {
856
+ inAppState.updateContent(123);
857
+ });
858
+
859
+ expect(consoleSpy).toHaveBeenCalledWith(
860
+ expect.stringContaining('newContent must be a string')
861
+ );
862
+
863
+ consoleSpy.mockRestore();
864
+ });
865
+
866
+ it('handles switchDevice with invalid device type', () => {
867
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
868
+ let inAppState;
869
+ render(<TestComponent onStateChange={(state) => { inAppState = state; }} />);
870
+
871
+ act(() => {
872
+ inAppState.switchDevice('InvalidDevice');
873
+ });
874
+
875
+ expect(consoleSpy).toHaveBeenCalledWith(
876
+ expect.stringContaining('Invalid device type')
877
+ );
878
+
879
+ consoleSpy.mockRestore();
880
+ });
881
+
882
+ it('handles auto-save with content length below minimum', () => {
883
+ const onSave = jest.fn();
884
+ let inAppState;
885
+ render(
886
+ <TestComponent
887
+ options={{ autoSave: true, autoSaveInterval: 100, onSave }}
888
+ onStateChange={(state) => { inAppState = state; }}
889
+ />
890
+ );
891
+
892
+ act(() => {
893
+ inAppState.updateContent(''); // Empty content
894
+ });
895
+
896
+ act(() => {
897
+ jest.advanceTimersByTime(200);
898
+ });
899
+
900
+ // onSave should not be called for empty content
901
+ expect(onSave).not.toHaveBeenCalled();
902
+ });
903
+
904
+ it('handles auto-save interval below minimum', () => {
905
+ const onSave = jest.fn();
906
+ let inAppState;
907
+ render(
908
+ <TestComponent
909
+ options={{ autoSave: true, autoSaveInterval: 500, onSave }} // Below 1000ms minimum
910
+ onStateChange={(state) => { inAppState = state; }}
911
+ />
912
+ );
913
+
914
+ act(() => {
915
+ inAppState.updateContent('<p>Content</p>');
916
+ });
917
+
918
+ act(() => {
919
+ jest.advanceTimersByTime(1000);
920
+ });
921
+
922
+ // onSave should not be called when interval is below minimum
923
+ expect(onSave).not.toHaveBeenCalled();
924
+ });
925
+ });
926
+ });
@@ -4,133 +4,30 @@
4
4
  * Manages separate HTML content for Android and iOS devices with sync functionality
5
5
  */
6
6
 
7
- import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
7
+ import {
8
+ useState, useCallback, useRef, useEffect, useMemo,
9
+ } from 'react';
8
10
  import { DEVICE_TYPES, PERFORMANCE } from '../constants';
9
11
 
10
12
  // Constants for better maintainability
11
13
  const CONTENT_VALIDATION = {
12
14
  MIN_CONTENT_LENGTH: 0,
13
- DEFAULT_CONTENT_TYPE: 'string'
15
+ DEFAULT_CONTENT_TYPE: 'string',
14
16
  };
15
17
 
16
18
  const AUTO_SAVE_CONFIG = {
17
19
  DEFAULT_ENABLED: true,
18
20
  DEFAULT_INTERVAL: PERFORMANCE.AUTO_SAVE_INTERVAL,
19
- MIN_AUTO_SAVE_INTERVAL_MS: 1000 // Minimum 1 second between auto-saves
21
+ MIN_AUTO_SAVE_INTERVAL_MS: 1000, // Minimum 1 second between auto-saves
20
22
  };
21
23
 
22
24
  /**
23
25
  * Default InApp content for different devices
26
+ * Empty strings - no default content for new templates
24
27
  */
25
28
  const DEFAULT_INAPP_CONTENT = {
26
- [DEVICE_TYPES.ANDROID]: `<!DOCTYPE html>
27
- <html lang="en">
28
- <head>
29
- <meta charset="UTF-8">
30
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
31
- <title>In-App Notification</title>
32
- <style>
33
- body {
34
- margin: 0;
35
- padding: 16px;
36
- font-family: -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif;
37
- background-color: #ffffff;
38
- color: #212121;
39
- }
40
- .notification {
41
- max-width: 100%;
42
- background: white;
43
- border-radius: 8px;
44
- padding: 16px;
45
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
46
- }
47
- .title {
48
- font-size: 16px;
49
- font-weight: 500;
50
- margin: 0 0 8px 0;
51
- color: #212121;
52
- }
53
- .message {
54
- font-size: 14px;
55
- line-height: 1.4;
56
- margin: 0 0 16px 0;
57
- color: #424242;
58
- }
59
- .cta-button {
60
- background-color: #42b040;
61
- color: white;
62
- border: none;
63
- border-radius: 4px;
64
- padding: 8px 16px;
65
- font-size: 12px;
66
- font-weight: 500;
67
- cursor: pointer;
68
- width: 100%;
69
- }
70
- </style>
71
- </head>
72
- <body>
73
- <div class="notification">
74
- <h2 class="title">Sample template</h2>
75
- <p class="message">This is a sample template for in-app notification content. This can be triggered on any behavioural event while the user is on the app</p>
76
- <button class="cta-button">Add to cart</button>
77
- </div>
78
- </body>
79
- </html>`,
80
- [DEVICE_TYPES.IOS]: `<!DOCTYPE html>
81
- <html lang="en">
82
- <head>
83
- <meta charset="UTF-8">
84
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
85
- <title>In-App Notification</title>
86
- <style>
87
- body {
88
- margin: 0;
89
- padding: 16px;
90
- font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif;
91
- background-color: #ffffff;
92
- color: #000000;
93
- }
94
- .notification {
95
- max-width: 100%;
96
- background: white;
97
- border-radius: 12px;
98
- padding: 16px;
99
- box-shadow: 0 4px 16px rgba(0,0,0,0.1);
100
- }
101
- .title {
102
- font-size: 17px;
103
- font-weight: 600;
104
- margin: 0 0 8px 0;
105
- color: #000000;
106
- }
107
- .message {
108
- font-size: 15px;
109
- line-height: 1.4;
110
- margin: 0 0 16px 0;
111
- color: #3c3c43;
112
- }
113
- .cta-button {
114
- background-color: #007AFF;
115
- color: white;
116
- border: none;
117
- border-radius: 8px;
118
- padding: 12px 16px;
119
- font-size: 16px;
120
- font-weight: 600;
121
- cursor: pointer;
122
- width: 100%;
123
- }
124
- </style>
125
- </head>
126
- <body>
127
- <div class="notification">
128
- <h2 class="title">Sample template</h2>
129
- <p class="message">This is a sample template for in-app notification content. This can be triggered on any behavioural event while the user is on the app</p>
130
- <button class="cta-button">Add to cart</button>
131
- </div>
132
- </body>
133
- </html>`
29
+ [DEVICE_TYPES.ANDROID]: '',
30
+ [DEVICE_TYPES.IOS]: '',
134
31
  };
135
32
 
136
33
  /**
@@ -150,7 +47,7 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
150
47
  autoSave = AUTO_SAVE_CONFIG.DEFAULT_ENABLED,
151
48
  autoSaveInterval = AUTO_SAVE_CONFIG.DEFAULT_INTERVAL,
152
49
  onSave,
153
- onChange
50
+ onChange,
154
51
  } = options;
155
52
 
156
53
  // Destructure device types for cleaner code
@@ -159,7 +56,7 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
159
56
  // Device-specific content state with optional chaining
160
57
  const [deviceContent, setDeviceContent] = useState(() => ({
161
58
  [ANDROID]: initialContent?.[ANDROID] || DEFAULT_INAPP_CONTENT[ANDROID],
162
- [IOS]: initialContent?.[IOS] || DEFAULT_INAPP_CONTENT[IOS]
59
+ [IOS]: initialContent?.[IOS] || DEFAULT_INAPP_CONTENT[IOS],
163
60
  }));
164
61
 
165
62
  // Current active device
@@ -189,15 +86,36 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
189
86
  deviceContentRef.current = deviceContent;
190
87
  }, [deviceContent]);
191
88
 
89
+ // Update content when initialContent prop changes (for edit mode)
90
+ useEffect(() => {
91
+ const newAndroidContent = initialContent?.[ANDROID];
92
+ const newIosContent = initialContent?.[IOS];
93
+
94
+ // Only update if initialContent has changed and is different from current content
95
+ setDeviceContent((prev) => {
96
+ let updated = false;
97
+ const updatedContent = { ...prev };
98
+
99
+ if (newAndroidContent !== undefined && newAndroidContent !== prev[ANDROID]) {
100
+ updatedContent[ANDROID] = newAndroidContent;
101
+ updated = true;
102
+ }
103
+ if (newIosContent !== undefined && newIosContent !== prev[IOS]) {
104
+ updatedContent[IOS] = newIosContent;
105
+ updated = true;
106
+ }
107
+
108
+ return updated ? updatedContent : prev;
109
+ });
110
+ }, [initialContent, ANDROID, IOS]);
111
+
192
112
  // Get current active content
193
- const currentContent = useMemo(() => {
194
- return deviceContent[activeDevice] || '';
195
- }, [deviceContent, activeDevice]);
113
+ const currentContent = useMemo(() => deviceContent?.[activeDevice] || '', [deviceContent, activeDevice]);
196
114
 
197
115
  // Update content for current device
198
116
  const updateContent = useCallback((newContent) => {
199
117
  // Validate input
200
- if (typeof newContent !== CONTENT_VALIDATION.DEFAULT_CONTENT_TYPE) {
118
+ if (typeof newContent !== CONTENT_VALIDATION?.DEFAULT_CONTENT_TYPE) {
201
119
  console.warn('useInAppContent: newContent must be a string');
202
120
  return;
203
121
  }
@@ -209,14 +127,14 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
209
127
  // When sync is enabled, update both devices with the same content
210
128
  updatedDeviceContent = {
211
129
  [ANDROID]: newContent,
212
- [IOS]: newContent
130
+ [IOS]: newContent,
213
131
  };
214
132
  } else {
215
133
  // When sync is disabled, update only the current device
216
- setDeviceContent(prev => {
134
+ setDeviceContent((prev) => {
217
135
  updatedDeviceContent = {
218
136
  ...prev,
219
- [activeDevice]: newContent
137
+ [activeDevice]: newContent,
220
138
  };
221
139
  return updatedDeviceContent;
222
140
  });
@@ -301,7 +219,7 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
301
219
  const currentActiveContent = deviceContent[activeDevice];
302
220
  const syncedContent = {
303
221
  [ANDROID]: currentActiveContent,
304
- [IOS]: currentActiveContent
222
+ [IOS]: currentActiveContent,
305
223
  };
306
224
 
307
225
  setDeviceContent(syncedContent);
@@ -325,22 +243,16 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
325
243
  }, [deviceContent, onSave]);
326
244
 
327
245
  // Check if content exists for current device
328
- const hasContent = useMemo(() => {
329
- return typeof currentContent === CONTENT_VALIDATION.DEFAULT_CONTENT_TYPE &&
330
- currentContent.trim().length > CONTENT_VALIDATION.MIN_CONTENT_LENGTH;
331
- }, [currentContent]);
246
+ const hasContent = useMemo(() => typeof currentContent === CONTENT_VALIDATION.DEFAULT_CONTENT_TYPE
247
+ && currentContent?.trim()?.length > CONTENT_VALIDATION.MIN_CONTENT_LENGTH, [currentContent]);
332
248
 
333
249
  // Get content size for current device
334
- const getContentSize = useCallback(() => {
335
- return typeof currentContent === CONTENT_VALIDATION.DEFAULT_CONTENT_TYPE
336
- ? currentContent.length
337
- : CONTENT_VALIDATION.MIN_CONTENT_LENGTH;
338
- }, [currentContent]);
250
+ const getContentSize = useCallback(() => typeof currentContent === CONTENT_VALIDATION.DEFAULT_CONTENT_TYPE
251
+ ? currentContent?.length
252
+ : CONTENT_VALIDATION?.MIN_CONTENT_LENGTH, [currentContent]);
339
253
 
340
254
  // Get content for specific device
341
- const getDeviceContent = useCallback((device) => {
342
- return deviceContent[device] || '';
343
- }, [deviceContent]);
255
+ const getDeviceContent = useCallback((device) => deviceContent[device] || '', [deviceContent]);
344
256
 
345
257
  // Set content for specific device
346
258
  const setDeviceContent_ = useCallback((device, content) => {
@@ -360,25 +272,23 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
360
272
  // Update both devices when sync is enabled
361
273
  setDeviceContent({
362
274
  [ANDROID]: content,
363
- [IOS]: content
275
+ [IOS]: content,
364
276
  });
365
277
  } else {
366
278
  // Update specific device
367
- setDeviceContent(prev => ({
279
+ setDeviceContent((prev) => ({
368
280
  ...prev,
369
- [device]: content
281
+ [device]: content,
370
282
  }));
371
283
  }
372
284
  setIsDirty(true);
373
285
  }, [keepContentSame, ANDROID, IOS]);
374
286
 
375
287
  // Cleanup on unmount
376
- useEffect(() => {
377
- return () => {
378
- if (autoSaveTimerRef.current) {
379
- clearTimeout(autoSaveTimerRef.current);
380
- }
381
- };
288
+ useEffect(() => () => {
289
+ if (autoSaveTimerRef.current) {
290
+ clearTimeout(autoSaveTimerRef.current);
291
+ }
382
292
  }, []);
383
293
 
384
294
  return {
@@ -402,6 +312,6 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
402
312
  toggleContentSync,
403
313
 
404
314
  // Save management
405
- markAsSaved
315
+ markAsSaved,
406
316
  };
407
317
  };
@@ -26,4 +26,4 @@ export { HTML_EDITOR_VARIANTS, DEVICE_TYPES } from './constants';
26
26
  * - Default export (lazy): ~0KB initial bundle (loads ~400KB when needed)
27
27
  * - HTMLEditorSync: ~400KB added to initial bundle
28
28
  * - Constants: ~1KB added to initial bundle
29
- */
29
+ */