@startsimpli/funnels 0.1.4 → 0.1.6

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 (151) hide show
  1. package/package.json +9 -31
  2. package/src/api/README.md +507 -0
  3. package/src/api/adapter.ts +106 -0
  4. package/src/api/client.test.ts +640 -0
  5. package/src/api/client.ts +385 -0
  6. package/src/api/default-adapter.ts +243 -0
  7. package/src/api/index.ts +24 -0
  8. package/src/components/FilterRuleEditor/ARCHITECTURE.md +354 -0
  9. package/src/components/FilterRuleEditor/FieldSelector.tsx +91 -0
  10. package/src/components/FilterRuleEditor/FilterRuleEditor.stories.tsx +462 -0
  11. package/src/components/FilterRuleEditor/FilterRuleEditor.test.tsx +520 -0
  12. package/src/components/FilterRuleEditor/FilterRuleEditor.tsx +225 -0
  13. package/src/components/FilterRuleEditor/LogicToggle.tsx +64 -0
  14. package/src/components/FilterRuleEditor/OperatorSelector.tsx +75 -0
  15. package/src/components/FilterRuleEditor/README.md +291 -0
  16. package/src/components/FilterRuleEditor/RuleRow.tsx +246 -0
  17. package/src/components/FilterRuleEditor/ValueInputs/BooleanValueInput.tsx +54 -0
  18. package/src/components/FilterRuleEditor/ValueInputs/ChoiceValueInput.tsx +83 -0
  19. package/src/components/FilterRuleEditor/ValueInputs/DateValueInput.tsx +70 -0
  20. package/src/components/FilterRuleEditor/ValueInputs/MultiChoiceValueInput.tsx +132 -0
  21. package/src/components/FilterRuleEditor/ValueInputs/NumberValueInput.tsx +73 -0
  22. package/src/components/FilterRuleEditor/ValueInputs/TextValueInput.tsx +50 -0
  23. package/src/components/FilterRuleEditor/ValueInputs/index.ts +12 -0
  24. package/src/components/FilterRuleEditor/constants.ts +64 -0
  25. package/src/components/FilterRuleEditor/index.ts +14 -0
  26. package/src/components/FunnelCard/DESIGN.md +447 -0
  27. package/src/components/FunnelCard/FunnelCard.stories.tsx +484 -0
  28. package/src/components/FunnelCard/FunnelCard.test.ts +257 -0
  29. package/src/components/FunnelCard/FunnelCard.test.tsx +336 -0
  30. package/src/components/FunnelCard/FunnelCard.tsx +204 -0
  31. package/src/components/FunnelCard/FunnelStats.tsx +68 -0
  32. package/src/components/FunnelCard/IMPLEMENTATION_SUMMARY.md +505 -0
  33. package/src/components/FunnelCard/INSTALLATION.md +304 -0
  34. package/src/components/FunnelCard/MatchBar.tsx +49 -0
  35. package/src/components/FunnelCard/README.md +294 -0
  36. package/src/components/FunnelCard/StageIndicator.tsx +62 -0
  37. package/src/components/FunnelCard/StatusBadge.tsx +52 -0
  38. package/src/components/FunnelCard/index.ts +14 -0
  39. package/src/components/FunnelPreview/EntityCard.tsx +72 -0
  40. package/src/components/FunnelPreview/FunnelPreview.stories.tsx +227 -0
  41. package/src/components/FunnelPreview/FunnelPreview.test.tsx +316 -0
  42. package/src/components/FunnelPreview/FunnelPreview.tsx +249 -0
  43. package/src/components/FunnelPreview/LoadingPreview.tsx +60 -0
  44. package/src/components/FunnelPreview/PreviewStats.tsx +78 -0
  45. package/src/components/FunnelPreview/README.md +337 -0
  46. package/src/components/FunnelPreview/StageBreakdown.tsx +94 -0
  47. package/src/components/FunnelPreview/example.tsx +286 -0
  48. package/src/components/FunnelPreview/index.ts +14 -0
  49. package/src/components/FunnelRunHistory/COMPONENT_SUMMARY.md +246 -0
  50. package/src/components/FunnelRunHistory/FunnelRunHistory.stories.tsx +272 -0
  51. package/src/components/FunnelRunHistory/FunnelRunHistory.test.tsx +323 -0
  52. package/src/components/FunnelRunHistory/FunnelRunHistory.tsx +329 -0
  53. package/src/components/FunnelRunHistory/README.md +325 -0
  54. package/src/components/FunnelRunHistory/RunActions.tsx +168 -0
  55. package/src/components/FunnelRunHistory/RunDetailsModal.tsx +221 -0
  56. package/src/components/FunnelRunHistory/RunFilters.tsx +128 -0
  57. package/src/components/FunnelRunHistory/RunRow.tsx +122 -0
  58. package/src/components/FunnelRunHistory/RunStatusBadge.tsx +75 -0
  59. package/src/components/FunnelRunHistory/StageBreakdownList.tsx +110 -0
  60. package/src/components/FunnelRunHistory/index.ts +51 -0
  61. package/src/components/FunnelRunHistory/types.ts +40 -0
  62. package/src/components/FunnelRunHistory/utils.test.ts +126 -0
  63. package/src/components/FunnelRunHistory/utils.ts +100 -0
  64. package/src/components/FunnelStageBuilder/AddStageButton.tsx +52 -0
  65. package/src/components/FunnelStageBuilder/FunnelStageBuilder.css +413 -0
  66. package/src/components/FunnelStageBuilder/FunnelStageBuilder.stories.tsx +312 -0
  67. package/src/components/FunnelStageBuilder/FunnelStageBuilder.test.tsx +304 -0
  68. package/src/components/FunnelStageBuilder/FunnelStageBuilder.tsx +321 -0
  69. package/src/components/FunnelStageBuilder/README.md +341 -0
  70. package/src/components/FunnelStageBuilder/StageActions.test.tsx +205 -0
  71. package/src/components/FunnelStageBuilder/StageActions.tsx +126 -0
  72. package/src/components/FunnelStageBuilder/StageCard.tsx +202 -0
  73. package/src/components/FunnelStageBuilder/StageForm.tsx +262 -0
  74. package/src/components/FunnelStageBuilder/TagInput.test.tsx +178 -0
  75. package/src/components/FunnelStageBuilder/TagInput.tsx +129 -0
  76. package/src/components/FunnelStageBuilder/index.ts +21 -0
  77. package/src/components/FunnelVisualFlow/FlowLegend.tsx +77 -0
  78. package/{dist/components/index.css → src/components/FunnelVisualFlow/FunnelVisualFlow.css} +89 -13
  79. package/src/components/FunnelVisualFlow/FunnelVisualFlow.stories.tsx +254 -0
  80. package/src/components/FunnelVisualFlow/FunnelVisualFlow.test.tsx +208 -0
  81. package/src/components/FunnelVisualFlow/FunnelVisualFlow.tsx +229 -0
  82. package/src/components/FunnelVisualFlow/README.md +323 -0
  83. package/src/components/FunnelVisualFlow/StageNode.tsx +188 -0
  84. package/src/components/FunnelVisualFlow/example.tsx +227 -0
  85. package/src/components/FunnelVisualFlow/index.ts +10 -0
  86. package/src/components/index.ts +102 -0
  87. package/src/core/README.md +307 -0
  88. package/src/core/engine.test.ts +1087 -0
  89. package/src/core/engine.ts +329 -0
  90. package/src/core/evaluator.example.ts +353 -0
  91. package/src/core/evaluator.test.ts +639 -0
  92. package/src/core/evaluator.ts +261 -0
  93. package/src/core/field-resolver.example.ts +175 -0
  94. package/src/core/field-resolver.test.ts +541 -0
  95. package/src/core/field-resolver.ts +247 -0
  96. package/src/core/index.ts +34 -0
  97. package/src/core/operators.test.ts +539 -0
  98. package/src/core/operators.ts +241 -0
  99. package/src/hooks/index.ts +5 -0
  100. package/src/hooks/useDebouncedValue.ts +28 -0
  101. package/src/index.ts +155 -0
  102. package/src/store/README.md +342 -0
  103. package/src/store/create-funnel-store.test.ts +686 -0
  104. package/src/store/create-funnel-store.ts +538 -0
  105. package/src/store/index.ts +9 -0
  106. package/src/store/types.ts +294 -0
  107. package/src/stories/CrossDomain.stories.tsx +149 -0
  108. package/src/stories/Welcome.stories.tsx +81 -0
  109. package/src/stories/demo-data/index.ts +3 -0
  110. package/src/stories/demo-data/investors.ts +216 -0
  111. package/src/stories/demo-data/leads.ts +223 -0
  112. package/src/stories/demo-data/recipes.ts +217 -0
  113. package/src/test/setup.ts +5 -0
  114. package/src/types/index.ts +843 -0
  115. package/dist/client-3ESO2NHy.d.ts +0 -310
  116. package/dist/client-CZu03ACp.d.cts +0 -310
  117. package/dist/components/index.cjs +0 -3241
  118. package/dist/components/index.cjs.map +0 -1
  119. package/dist/components/index.css.map +0 -1
  120. package/dist/components/index.d.cts +0 -726
  121. package/dist/components/index.d.ts +0 -726
  122. package/dist/components/index.js +0 -3194
  123. package/dist/components/index.js.map +0 -1
  124. package/dist/core/index.cjs +0 -500
  125. package/dist/core/index.cjs.map +0 -1
  126. package/dist/core/index.d.cts +0 -359
  127. package/dist/core/index.d.ts +0 -359
  128. package/dist/core/index.js +0 -486
  129. package/dist/core/index.js.map +0 -1
  130. package/dist/hooks/index.cjs +0 -20
  131. package/dist/hooks/index.cjs.map +0 -1
  132. package/dist/hooks/index.d.cts +0 -11
  133. package/dist/hooks/index.d.ts +0 -11
  134. package/dist/hooks/index.js +0 -18
  135. package/dist/hooks/index.js.map +0 -1
  136. package/dist/index-BGDEXbuz.d.cts +0 -434
  137. package/dist/index-BGDEXbuz.d.ts +0 -434
  138. package/dist/index.cjs +0 -4499
  139. package/dist/index.cjs.map +0 -1
  140. package/dist/index.css +0 -198
  141. package/dist/index.css.map +0 -1
  142. package/dist/index.d.cts +0 -99
  143. package/dist/index.d.ts +0 -99
  144. package/dist/index.js +0 -4421
  145. package/dist/index.js.map +0 -1
  146. package/dist/store/index.cjs +0 -389
  147. package/dist/store/index.cjs.map +0 -1
  148. package/dist/store/index.d.cts +0 -225
  149. package/dist/store/index.d.ts +0 -225
  150. package/dist/store/index.js +0 -386
  151. package/dist/store/index.js.map +0 -1
@@ -1,225 +0,0 @@
1
- import { UseBoundStore, StoreApi } from 'zustand';
2
- import { b as FunnelListFilters, F as FunnelApiClient } from '../client-3ESO2NHy.js';
3
- import { i as Funnel, m as FunnelStage, k as FunnelRun, C as CreateFunnelInput, U as UpdateFunnelInput, a as CreateStageInput, p as UpdateStageInput } from '../index-BGDEXbuz.js';
4
-
5
- /**
6
- * Store types for @startsimpli/funnels
7
- *
8
- * Generic Zustand store for managing funnel state.
9
- * Works with any entity type - no product-specific logic.
10
- */
11
-
12
- /**
13
- * Store state interface
14
- *
15
- * Generic over entity type TEntity
16
- */
17
- interface FunnelStore<TEntity = any> {
18
- /** All loaded funnels */
19
- funnels: Funnel<TEntity>[];
20
- /** Currently selected funnel */
21
- selectedFunnel: Funnel<TEntity> | null;
22
- /** Currently selected stage (for editing) */
23
- selectedStage: FunnelStage<TEntity> | null;
24
- /** Funnel runs for selected funnel */
25
- runs: FunnelRun[];
26
- /** Pagination info */
27
- pagination: {
28
- count: number;
29
- next: string | null;
30
- previous: string | null;
31
- currentPage: number;
32
- pageSize: number;
33
- };
34
- /** Loading state */
35
- isLoading: boolean;
36
- /** Error state */
37
- error: Error | null;
38
- /** Dirty flag - unsaved changes */
39
- isDirty: boolean;
40
- /** Optimistic update rollback state */
41
- rollbackState: {
42
- funnels: Funnel<TEntity>[];
43
- selectedFunnel: Funnel<TEntity> | null;
44
- } | null;
45
- /**
46
- * Load funnels with optional filters
47
- *
48
- * @param filters - Optional filters (status, owner, pagination, etc)
49
- */
50
- loadFunnels: (filters?: FunnelListFilters) => Promise<void>;
51
- /**
52
- * Select funnel for editing
53
- *
54
- * @param id - Funnel ID
55
- */
56
- selectFunnel: (id: string | null) => void;
57
- /**
58
- * Create new funnel
59
- *
60
- * @param data - Funnel creation data
61
- * @returns Created funnel
62
- */
63
- createFunnel: (data: CreateFunnelInput<TEntity>) => Promise<Funnel<TEntity>>;
64
- /**
65
- * Update funnel (optimistic update)
66
- *
67
- * @param id - Funnel ID
68
- * @param data - Update data
69
- * @returns Updated funnel
70
- */
71
- updateFunnel: (id: string, data: Partial<UpdateFunnelInput<TEntity>>) => Promise<Funnel<TEntity>>;
72
- /**
73
- * Delete funnel (optimistic update)
74
- *
75
- * @param id - Funnel ID
76
- */
77
- deleteFunnel: (id: string) => Promise<void>;
78
- /**
79
- * Duplicate funnel
80
- *
81
- * @param id - Funnel ID to duplicate
82
- * @returns New funnel
83
- */
84
- duplicateFunnel: (id: string) => Promise<Funnel<TEntity>>;
85
- /**
86
- * Select stage for editing
87
- *
88
- * @param stageId - Stage ID (null to clear selection)
89
- */
90
- selectStage: (stageId: string | null) => void;
91
- /**
92
- * Create new stage in funnel
93
- *
94
- * @param funnelId - Funnel ID
95
- * @param data - Stage creation data
96
- * @returns Created stage
97
- */
98
- createStage: (funnelId: string, data: CreateStageInput<TEntity>) => Promise<FunnelStage<TEntity>>;
99
- /**
100
- * Update stage (optimistic update)
101
- *
102
- * @param funnelId - Funnel ID
103
- * @param stageId - Stage ID
104
- * @param data - Update data
105
- * @returns Updated stage
106
- */
107
- updateStage: (funnelId: string, stageId: string, data: Partial<UpdateStageInput<TEntity>>) => Promise<FunnelStage<TEntity>>;
108
- /**
109
- * Delete stage (optimistic update)
110
- *
111
- * @param funnelId - Funnel ID
112
- * @param stageId - Stage ID
113
- */
114
- deleteStage: (funnelId: string, stageId: string) => Promise<void>;
115
- /**
116
- * Reorder stages (optimistic update)
117
- *
118
- * @param funnelId - Funnel ID
119
- * @param stageIds - New order of stage IDs
120
- */
121
- reorderStages: (funnelId: string, stageIds: string[]) => Promise<void>;
122
- /**
123
- * Run funnel
124
- *
125
- * @param id - Funnel ID
126
- * @param options - Run options
127
- * @returns Created run
128
- */
129
- runFunnel: (id: string, options?: {
130
- trigger_type?: 'manual' | 'scheduled' | 'webhook' | 'api';
131
- metadata?: Record<string, any>;
132
- }) => Promise<FunnelRun>;
133
- /**
134
- * Load funnel runs
135
- *
136
- * @param funnelId - Funnel ID
137
- * @param filters - Optional filters
138
- */
139
- loadRuns: (funnelId: string, filters?: {
140
- status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
141
- page?: number;
142
- page_size?: number;
143
- }) => Promise<void>;
144
- /**
145
- * Cancel running funnel
146
- *
147
- * @param runId - Run ID
148
- */
149
- cancelRun: (runId: string) => Promise<FunnelRun>;
150
- /**
151
- * Set dirty flag
152
- *
153
- * @param dirty - Dirty state
154
- */
155
- setDirty: (dirty: boolean) => void;
156
- /**
157
- * Clear error
158
- */
159
- clearError: () => void;
160
- /**
161
- * Reset store to initial state
162
- */
163
- reset: () => void;
164
- /**
165
- * Save current state for rollback
166
- * @internal
167
- */
168
- _saveRollbackState: () => void;
169
- /**
170
- * Rollback to saved state
171
- * @internal
172
- */
173
- _rollback: () => void;
174
- /**
175
- * Clear rollback state
176
- * @internal
177
- */
178
- _clearRollback: () => void;
179
- }
180
- /**
181
- * Initial state factory
182
- */
183
- declare const createInitialState: <TEntity = any>() => Pick<FunnelStore<TEntity>, "funnels" | "selectedFunnel" | "selectedStage" | "runs" | "pagination" | "isLoading" | "error" | "isDirty" | "rollbackState">;
184
-
185
- /**
186
- * Funnel Store Factory
187
- *
188
- * Creates isolated Zustand store instances for funnel management.
189
- * Factory pattern allows multiple editors to coexist without state conflicts.
190
- *
191
- * Features:
192
- * - Optimistic updates with automatic rollback on error
193
- * - Dirty tracking for unsaved changes
194
- * - Loading states for better UX
195
- * - Error handling with automatic retry
196
- * - Selection state management
197
- *
198
- * @packageDocumentation
199
- */
200
-
201
- /**
202
- * Create funnel store with API client
203
- *
204
- * Factory pattern - each call creates a new isolated store instance.
205
- * Perfect for multi-editor scenarios or testing.
206
- *
207
- * @param apiClient - API client for server operations
208
- * @returns Zustand store hook
209
- *
210
- * @example
211
- * ```ts
212
- * const adapter = new FetchAdapter({ headers: { 'Authorization': 'Bearer token' } });
213
- * const apiClient = new FunnelApiClient(adapter, 'https://api.example.com');
214
- * const useFunnelStore = createFunnelStore(apiClient);
215
- *
216
- * // In React component
217
- * function FunnelEditor() {
218
- * const { funnels, loadFunnels, createFunnel } = useFunnelStore();
219
- * // ...
220
- * }
221
- * ```
222
- */
223
- declare function createFunnelStore<TEntity = any>(apiClient: FunnelApiClient): UseBoundStore<StoreApi<FunnelStore<TEntity>>>;
224
-
225
- export { type FunnelStore, createFunnelStore, createInitialState };
@@ -1,386 +0,0 @@
1
- import { create } from 'zustand';
2
-
3
- // src/store/types.ts
4
- var createInitialState = () => ({
5
- funnels: [],
6
- selectedFunnel: null,
7
- selectedStage: null,
8
- runs: [],
9
- pagination: {
10
- count: 0,
11
- next: null,
12
- previous: null,
13
- currentPage: 1,
14
- pageSize: 20
15
- },
16
- isLoading: false,
17
- error: null,
18
- isDirty: false,
19
- rollbackState: null
20
- });
21
-
22
- // src/store/create-funnel-store.ts
23
- function createFunnelStore(apiClient) {
24
- return create((set, get) => ({
25
- // Initialize state
26
- ...createInitialState(),
27
- // =========================================================================
28
- // Funnel Actions
29
- // =========================================================================
30
- loadFunnels: async (filters) => {
31
- set({ isLoading: true, error: null });
32
- try {
33
- const response = await apiClient.listFunnels(filters);
34
- set({
35
- funnels: response.results,
36
- pagination: {
37
- count: response.count,
38
- next: response.next,
39
- previous: response.previous,
40
- currentPage: filters?.page || 1,
41
- pageSize: filters?.page_size || 20
42
- },
43
- isLoading: false
44
- });
45
- } catch (error) {
46
- set({
47
- error,
48
- isLoading: false
49
- });
50
- throw error;
51
- }
52
- },
53
- selectFunnel: (id) => {
54
- const { funnels } = get();
55
- if (id === null) {
56
- set({ selectedFunnel: null, selectedStage: null });
57
- return;
58
- }
59
- const funnel = funnels.find((f) => f.id === id);
60
- set({
61
- selectedFunnel: funnel || null,
62
- selectedStage: null
63
- // Clear stage selection when changing funnels
64
- });
65
- },
66
- createFunnel: async (data) => {
67
- set({ isLoading: true, error: null });
68
- try {
69
- const funnel = await apiClient.createFunnel(data);
70
- set((state) => ({
71
- funnels: [...state.funnels, funnel],
72
- pagination: {
73
- ...state.pagination,
74
- count: state.pagination.count + 1
75
- },
76
- isLoading: false
77
- }));
78
- return funnel;
79
- } catch (error) {
80
- set({
81
- error,
82
- isLoading: false
83
- });
84
- throw error;
85
- }
86
- },
87
- updateFunnel: async (id, data) => {
88
- get()._saveRollbackState();
89
- set((state) => ({
90
- funnels: state.funnels.map(
91
- (f) => f.id === id ? { ...f, ...data } : f
92
- ),
93
- selectedFunnel: state.selectedFunnel?.id === id ? { ...state.selectedFunnel, ...data } : state.selectedFunnel,
94
- isDirty: false
95
- // Clear dirty flag on save
96
- }));
97
- try {
98
- const updated = await apiClient.updateFunnel(id, data);
99
- set((state) => ({
100
- funnels: state.funnels.map((f) => f.id === id ? updated : f),
101
- selectedFunnel: state.selectedFunnel?.id === id ? updated : state.selectedFunnel
102
- }));
103
- get()._clearRollback();
104
- return updated;
105
- } catch (error) {
106
- get()._rollback();
107
- set({ error });
108
- throw error;
109
- }
110
- },
111
- deleteFunnel: async (id) => {
112
- get()._saveRollbackState();
113
- set((state) => ({
114
- funnels: state.funnels.filter((f) => f.id !== id),
115
- selectedFunnel: state.selectedFunnel?.id === id ? null : state.selectedFunnel,
116
- pagination: {
117
- ...state.pagination,
118
- count: state.pagination.count - 1
119
- }
120
- }));
121
- try {
122
- await apiClient.deleteFunnel(id);
123
- get()._clearRollback();
124
- } catch (error) {
125
- get()._rollback();
126
- set({ error });
127
- throw error;
128
- }
129
- },
130
- duplicateFunnel: async (id) => {
131
- const { funnels } = get();
132
- const funnel = funnels.find((f) => f.id === id);
133
- if (!funnel) {
134
- throw new Error(`Funnel ${id} not found`);
135
- }
136
- const copy = {
137
- name: `${funnel.name} (Copy)`,
138
- description: funnel.description,
139
- status: "draft",
140
- // Always create as draft
141
- input_type: funnel.input_type,
142
- stages: funnel.stages.map((stage, index) => ({
143
- ...stage,
144
- order: index
145
- // Preserve order
146
- })),
147
- completion_tags: funnel.completion_tags,
148
- metadata: funnel.metadata
149
- };
150
- return get().createFunnel(copy);
151
- },
152
- // =========================================================================
153
- // Stage Actions
154
- // =========================================================================
155
- selectStage: (stageId) => {
156
- const { selectedFunnel } = get();
157
- if (!selectedFunnel) {
158
- set({ selectedStage: null });
159
- return;
160
- }
161
- if (stageId === null) {
162
- set({ selectedStage: null });
163
- return;
164
- }
165
- const stage = selectedFunnel.stages.find((s) => s.id === stageId);
166
- set({ selectedStage: stage || null });
167
- },
168
- createStage: async (funnelId, data) => {
169
- set({ isLoading: true, error: null });
170
- try {
171
- const stage = await apiClient.createStage(funnelId, data);
172
- set((state) => ({
173
- funnels: state.funnels.map(
174
- (f) => f.id === funnelId ? { ...f, stages: [...f.stages, stage] } : f
175
- ),
176
- selectedFunnel: state.selectedFunnel?.id === funnelId ? { ...state.selectedFunnel, stages: [...state.selectedFunnel.stages, stage] } : state.selectedFunnel,
177
- isLoading: false
178
- }));
179
- return stage;
180
- } catch (error) {
181
- set({
182
- error,
183
- isLoading: false
184
- });
185
- throw error;
186
- }
187
- },
188
- updateStage: async (funnelId, stageId, data) => {
189
- get()._saveRollbackState();
190
- set((state) => ({
191
- funnels: state.funnels.map(
192
- (f) => f.id === funnelId ? {
193
- ...f,
194
- stages: f.stages.map(
195
- (s) => s.id === stageId ? { ...s, ...data } : s
196
- )
197
- } : f
198
- ),
199
- selectedFunnel: state.selectedFunnel?.id === funnelId ? {
200
- ...state.selectedFunnel,
201
- stages: state.selectedFunnel.stages.map(
202
- (s) => s.id === stageId ? { ...s, ...data } : s
203
- )
204
- } : state.selectedFunnel,
205
- selectedStage: state.selectedStage?.id === stageId ? { ...state.selectedStage, ...data } : state.selectedStage,
206
- isDirty: false
207
- }));
208
- try {
209
- const updated = await apiClient.updateStage(
210
- funnelId,
211
- stageId,
212
- data
213
- );
214
- set((state) => ({
215
- funnels: state.funnels.map(
216
- (f) => f.id === funnelId ? {
217
- ...f,
218
- stages: f.stages.map((s) => s.id === stageId ? updated : s)
219
- } : f
220
- ),
221
- selectedFunnel: state.selectedFunnel?.id === funnelId ? {
222
- ...state.selectedFunnel,
223
- stages: state.selectedFunnel.stages.map(
224
- (s) => s.id === stageId ? updated : s
225
- )
226
- } : state.selectedFunnel,
227
- selectedStage: state.selectedStage?.id === stageId ? updated : state.selectedStage
228
- }));
229
- get()._clearRollback();
230
- return updated;
231
- } catch (error) {
232
- get()._rollback();
233
- set({ error });
234
- throw error;
235
- }
236
- },
237
- deleteStage: async (funnelId, stageId) => {
238
- get()._saveRollbackState();
239
- set((state) => ({
240
- funnels: state.funnels.map(
241
- (f) => f.id === funnelId ? {
242
- ...f,
243
- stages: f.stages.filter((s) => s.id !== stageId)
244
- } : f
245
- ),
246
- selectedFunnel: state.selectedFunnel?.id === funnelId ? {
247
- ...state.selectedFunnel,
248
- stages: state.selectedFunnel.stages.filter((s) => s.id !== stageId)
249
- } : state.selectedFunnel,
250
- selectedStage: state.selectedStage?.id === stageId ? null : state.selectedStage
251
- }));
252
- try {
253
- await apiClient.deleteStage(funnelId, stageId);
254
- get()._clearRollback();
255
- } catch (error) {
256
- get()._rollback();
257
- set({ error });
258
- throw error;
259
- }
260
- },
261
- reorderStages: async (funnelId, stageIds) => {
262
- const { selectedFunnel } = get();
263
- if (!selectedFunnel || selectedFunnel.id !== funnelId) {
264
- throw new Error("Funnel must be selected to reorder stages");
265
- }
266
- get()._saveRollbackState();
267
- const reorderedStages = stageIds.map((id, index) => {
268
- const stage = selectedFunnel.stages.find((s) => s.id === id);
269
- return stage ? { ...stage, order: index } : null;
270
- }).filter((s) => s !== null);
271
- set((state) => ({
272
- funnels: state.funnels.map(
273
- (f) => f.id === funnelId ? { ...f, stages: reorderedStages } : f
274
- ),
275
- selectedFunnel: { ...selectedFunnel, stages: reorderedStages },
276
- isDirty: false
277
- }));
278
- try {
279
- await Promise.all(
280
- reorderedStages.map(
281
- (stage) => apiClient.updateStage(funnelId, stage.id, { order: stage.order })
282
- )
283
- );
284
- get()._clearRollback();
285
- } catch (error) {
286
- get()._rollback();
287
- set({ error });
288
- throw error;
289
- }
290
- },
291
- // =========================================================================
292
- // Run Actions
293
- // =========================================================================
294
- runFunnel: async (id, options) => {
295
- set({ isLoading: true, error: null });
296
- try {
297
- const run = await apiClient.runFunnel(id, options);
298
- set((state) => ({
299
- runs: [run, ...state.runs],
300
- isLoading: false
301
- }));
302
- return run;
303
- } catch (error) {
304
- set({
305
- error,
306
- isLoading: false
307
- });
308
- throw error;
309
- }
310
- },
311
- loadRuns: async (funnelId, filters) => {
312
- set({ isLoading: true, error: null });
313
- try {
314
- const response = await apiClient.getFunnelRuns(funnelId, filters);
315
- set({
316
- runs: response.results,
317
- isLoading: false
318
- });
319
- } catch (error) {
320
- set({
321
- error,
322
- isLoading: false
323
- });
324
- throw error;
325
- }
326
- },
327
- cancelRun: async (runId) => {
328
- set({ isLoading: true, error: null });
329
- try {
330
- const run = await apiClient.cancelFunnelRun(runId);
331
- set((state) => ({
332
- runs: state.runs.map((r) => r.id === runId ? run : r),
333
- isLoading: false
334
- }));
335
- return run;
336
- } catch (error) {
337
- set({
338
- error,
339
- isLoading: false
340
- });
341
- throw error;
342
- }
343
- },
344
- // =========================================================================
345
- // UI State Actions
346
- // =========================================================================
347
- setDirty: (dirty) => {
348
- set({ isDirty: dirty });
349
- },
350
- clearError: () => {
351
- set({ error: null });
352
- },
353
- reset: () => {
354
- set(createInitialState());
355
- },
356
- // =========================================================================
357
- // Internal Actions (Optimistic Updates)
358
- // =========================================================================
359
- _saveRollbackState: () => {
360
- const { funnels, selectedFunnel } = get();
361
- set({
362
- rollbackState: {
363
- funnels: JSON.parse(JSON.stringify(funnels)),
364
- selectedFunnel: selectedFunnel ? JSON.parse(JSON.stringify(selectedFunnel)) : null
365
- }
366
- });
367
- },
368
- _rollback: () => {
369
- const { rollbackState } = get();
370
- if (rollbackState) {
371
- set({
372
- funnels: rollbackState.funnels,
373
- selectedFunnel: rollbackState.selectedFunnel,
374
- rollbackState: null
375
- });
376
- }
377
- },
378
- _clearRollback: () => {
379
- set({ rollbackState: null });
380
- }
381
- }));
382
- }
383
-
384
- export { createFunnelStore, createInitialState };
385
- //# sourceMappingURL=index.js.map
386
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/store/types.ts","../../src/store/create-funnel-store.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Store types for @startsimpli/funnels\n *\n * Generic Zustand store for managing funnel state.\n * Works with any entity type - no product-specific logic.\n */\n\nimport type {\n Funnel,\n FunnelStage,\n FunnelRun,\n CreateFunnelInput,\n UpdateFunnelInput,\n CreateStageInput,\n UpdateStageInput,\n} from '../types';\n\nimport type {\n FunnelListFilters,\n PaginatedResponse,\n} from '../api/client';\n\n/**\n * Store state interface\n *\n * Generic over entity type TEntity\n */\nexport interface FunnelStore<TEntity = any> {\n // ============================================================================\n // Data State\n // ============================================================================\n\n /** All loaded funnels */\n funnels: Funnel<TEntity>[];\n\n /** Currently selected funnel */\n selectedFunnel: Funnel<TEntity> | null;\n\n /** Currently selected stage (for editing) */\n selectedStage: FunnelStage<TEntity> | null;\n\n /** Funnel runs for selected funnel */\n runs: FunnelRun[];\n\n /** Pagination info */\n pagination: {\n count: number;\n next: string | null;\n previous: string | null;\n currentPage: number;\n pageSize: number;\n };\n\n // ============================================================================\n // UI State\n // ============================================================================\n\n /** Loading state */\n isLoading: boolean;\n\n /** Error state */\n error: Error | null;\n\n /** Dirty flag - unsaved changes */\n isDirty: boolean;\n\n /** Optimistic update rollback state */\n rollbackState: {\n funnels: Funnel<TEntity>[];\n selectedFunnel: Funnel<TEntity> | null;\n } | null;\n\n // ============================================================================\n // Funnel Actions\n // ============================================================================\n\n /**\n * Load funnels with optional filters\n *\n * @param filters - Optional filters (status, owner, pagination, etc)\n */\n loadFunnels: (filters?: FunnelListFilters) => Promise<void>;\n\n /**\n * Select funnel for editing\n *\n * @param id - Funnel ID\n */\n selectFunnel: (id: string | null) => void;\n\n /**\n * Create new funnel\n *\n * @param data - Funnel creation data\n * @returns Created funnel\n */\n createFunnel: (data: CreateFunnelInput<TEntity>) => Promise<Funnel<TEntity>>;\n\n /**\n * Update funnel (optimistic update)\n *\n * @param id - Funnel ID\n * @param data - Update data\n * @returns Updated funnel\n */\n updateFunnel: (\n id: string,\n data: Partial<UpdateFunnelInput<TEntity>>\n ) => Promise<Funnel<TEntity>>;\n\n /**\n * Delete funnel (optimistic update)\n *\n * @param id - Funnel ID\n */\n deleteFunnel: (id: string) => Promise<void>;\n\n /**\n * Duplicate funnel\n *\n * @param id - Funnel ID to duplicate\n * @returns New funnel\n */\n duplicateFunnel: (id: string) => Promise<Funnel<TEntity>>;\n\n // ============================================================================\n // Stage Actions\n // ============================================================================\n\n /**\n * Select stage for editing\n *\n * @param stageId - Stage ID (null to clear selection)\n */\n selectStage: (stageId: string | null) => void;\n\n /**\n * Create new stage in funnel\n *\n * @param funnelId - Funnel ID\n * @param data - Stage creation data\n * @returns Created stage\n */\n createStage: (\n funnelId: string,\n data: CreateStageInput<TEntity>\n ) => Promise<FunnelStage<TEntity>>;\n\n /**\n * Update stage (optimistic update)\n *\n * @param funnelId - Funnel ID\n * @param stageId - Stage ID\n * @param data - Update data\n * @returns Updated stage\n */\n updateStage: (\n funnelId: string,\n stageId: string,\n data: Partial<UpdateStageInput<TEntity>>\n ) => Promise<FunnelStage<TEntity>>;\n\n /**\n * Delete stage (optimistic update)\n *\n * @param funnelId - Funnel ID\n * @param stageId - Stage ID\n */\n deleteStage: (funnelId: string, stageId: string) => Promise<void>;\n\n /**\n * Reorder stages (optimistic update)\n *\n * @param funnelId - Funnel ID\n * @param stageIds - New order of stage IDs\n */\n reorderStages: (funnelId: string, stageIds: string[]) => Promise<void>;\n\n // ============================================================================\n // Run Actions\n // ============================================================================\n\n /**\n * Run funnel\n *\n * @param id - Funnel ID\n * @param options - Run options\n * @returns Created run\n */\n runFunnel: (\n id: string,\n options?: {\n trigger_type?: 'manual' | 'scheduled' | 'webhook' | 'api';\n metadata?: Record<string, any>;\n }\n ) => Promise<FunnelRun>;\n\n /**\n * Load funnel runs\n *\n * @param funnelId - Funnel ID\n * @param filters - Optional filters\n */\n loadRuns: (\n funnelId: string,\n filters?: {\n status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';\n page?: number;\n page_size?: number;\n }\n ) => Promise<void>;\n\n /**\n * Cancel running funnel\n *\n * @param runId - Run ID\n */\n cancelRun: (runId: string) => Promise<FunnelRun>;\n\n // ============================================================================\n // UI State Actions\n // ============================================================================\n\n /**\n * Set dirty flag\n *\n * @param dirty - Dirty state\n */\n setDirty: (dirty: boolean) => void;\n\n /**\n * Clear error\n */\n clearError: () => void;\n\n /**\n * Reset store to initial state\n */\n reset: () => void;\n\n // ============================================================================\n // Internal Actions (for optimistic updates)\n // ============================================================================\n\n /**\n * Save current state for rollback\n * @internal\n */\n _saveRollbackState: () => void;\n\n /**\n * Rollback to saved state\n * @internal\n */\n _rollback: () => void;\n\n /**\n * Clear rollback state\n * @internal\n */\n _clearRollback: () => void;\n}\n\n/**\n * Initial state factory\n */\nexport const createInitialState = <TEntity = any>(): Pick<\n FunnelStore<TEntity>,\n | 'funnels'\n | 'selectedFunnel'\n | 'selectedStage'\n | 'runs'\n | 'pagination'\n | 'isLoading'\n | 'error'\n | 'isDirty'\n | 'rollbackState'\n> => ({\n funnels: [],\n selectedFunnel: null,\n selectedStage: null,\n runs: [],\n pagination: {\n count: 0,\n next: null,\n previous: null,\n currentPage: 1,\n pageSize: 20,\n },\n isLoading: false,\n error: null,\n isDirty: false,\n rollbackState: null,\n});\n","/**\n * Funnel Store Factory\n *\n * Creates isolated Zustand store instances for funnel management.\n * Factory pattern allows multiple editors to coexist without state conflicts.\n *\n * Features:\n * - Optimistic updates with automatic rollback on error\n * - Dirty tracking for unsaved changes\n * - Loading states for better UX\n * - Error handling with automatic retry\n * - Selection state management\n *\n * @packageDocumentation\n */\n\nimport { create, StoreApi, UseBoundStore } from 'zustand';\nimport type { FunnelApiClient, FunnelListFilters } from '../api/client';\nimport type { FunnelStore } from './types';\nimport { createInitialState } from './types';\nimport type {\n Funnel,\n FunnelStage,\n FunnelRun,\n CreateFunnelInput,\n UpdateFunnelInput,\n CreateStageInput,\n UpdateStageInput,\n} from '../types';\n\n/**\n * Create funnel store with API client\n *\n * Factory pattern - each call creates a new isolated store instance.\n * Perfect for multi-editor scenarios or testing.\n *\n * @param apiClient - API client for server operations\n * @returns Zustand store hook\n *\n * @example\n * ```ts\n * const adapter = new FetchAdapter({ headers: { 'Authorization': 'Bearer token' } });\n * const apiClient = new FunnelApiClient(adapter, 'https://api.example.com');\n * const useFunnelStore = createFunnelStore(apiClient);\n *\n * // In React component\n * function FunnelEditor() {\n * const { funnels, loadFunnels, createFunnel } = useFunnelStore();\n * // ...\n * }\n * ```\n */\nexport function createFunnelStore<TEntity = any>(\n apiClient: FunnelApiClient\n): UseBoundStore<StoreApi<FunnelStore<TEntity>>> {\n return create<FunnelStore<TEntity>>((set, get) => ({\n // Initialize state\n ...createInitialState<TEntity>(),\n\n // =========================================================================\n // Funnel Actions\n // =========================================================================\n\n loadFunnels: async (filters?: FunnelListFilters) => {\n set({ isLoading: true, error: null });\n\n try {\n const response = await apiClient.listFunnels<TEntity>(filters);\n\n set({\n funnels: response.results,\n pagination: {\n count: response.count,\n next: response.next,\n previous: response.previous,\n currentPage: filters?.page || 1,\n pageSize: filters?.page_size || 20,\n },\n isLoading: false,\n });\n } catch (error) {\n set({\n error: error as Error,\n isLoading: false,\n });\n throw error;\n }\n },\n\n selectFunnel: (id: string | null) => {\n const { funnels } = get();\n\n if (id === null) {\n set({ selectedFunnel: null, selectedStage: null });\n return;\n }\n\n const funnel = funnels.find((f) => f.id === id);\n set({\n selectedFunnel: funnel || null,\n selectedStage: null, // Clear stage selection when changing funnels\n });\n },\n\n createFunnel: async (data: CreateFunnelInput<TEntity>) => {\n set({ isLoading: true, error: null });\n\n try {\n const funnel = await apiClient.createFunnel<TEntity>(data);\n\n set((state) => ({\n funnels: [...state.funnels, funnel],\n pagination: {\n ...state.pagination,\n count: state.pagination.count + 1,\n },\n isLoading: false,\n }));\n\n return funnel;\n } catch (error) {\n set({\n error: error as Error,\n isLoading: false,\n });\n throw error;\n }\n },\n\n updateFunnel: async (\n id: string,\n data: Partial<UpdateFunnelInput<TEntity>>\n ) => {\n // Save state for rollback\n get()._saveRollbackState();\n\n // Optimistic update\n set((state) => ({\n funnels: state.funnels.map((f) =>\n f.id === id ? ({ ...f, ...data } as Funnel<TEntity>) : f\n ),\n selectedFunnel:\n state.selectedFunnel?.id === id\n ? ({ ...state.selectedFunnel, ...data } as Funnel<TEntity>)\n : state.selectedFunnel,\n isDirty: false, // Clear dirty flag on save\n }));\n\n try {\n const updated = await apiClient.updateFunnel<TEntity>(id, data);\n\n // Update with server response\n set((state) => ({\n funnels: state.funnels.map((f) => (f.id === id ? updated : f)),\n selectedFunnel:\n state.selectedFunnel?.id === id ? updated : state.selectedFunnel,\n }));\n\n get()._clearRollback();\n return updated;\n } catch (error) {\n // Rollback optimistic update\n get()._rollback();\n set({ error: error as Error });\n throw error;\n }\n },\n\n deleteFunnel: async (id: string) => {\n // Save state for rollback\n get()._saveRollbackState();\n\n // Optimistic update\n set((state) => ({\n funnels: state.funnels.filter((f) => f.id !== id),\n selectedFunnel: state.selectedFunnel?.id === id ? null : state.selectedFunnel,\n pagination: {\n ...state.pagination,\n count: state.pagination.count - 1,\n },\n }));\n\n try {\n await apiClient.deleteFunnel(id);\n get()._clearRollback();\n } catch (error) {\n // Rollback optimistic update\n get()._rollback();\n set({ error: error as Error });\n throw error;\n }\n },\n\n duplicateFunnel: async (id: string) => {\n const { funnels } = get();\n const funnel = funnels.find((f) => f.id === id);\n\n if (!funnel) {\n throw new Error(`Funnel ${id} not found`);\n }\n\n // Create copy with new name\n const copy: CreateFunnelInput<TEntity> = {\n name: `${funnel.name} (Copy)`,\n description: funnel.description,\n status: 'draft', // Always create as draft\n input_type: funnel.input_type,\n stages: funnel.stages.map((stage, index) => ({\n ...stage,\n order: index, // Preserve order\n })),\n completion_tags: funnel.completion_tags,\n metadata: funnel.metadata,\n };\n\n return get().createFunnel(copy);\n },\n\n // =========================================================================\n // Stage Actions\n // =========================================================================\n\n selectStage: (stageId: string | null) => {\n const { selectedFunnel } = get();\n\n if (!selectedFunnel) {\n set({ selectedStage: null });\n return;\n }\n\n if (stageId === null) {\n set({ selectedStage: null });\n return;\n }\n\n const stage = selectedFunnel.stages.find((s) => s.id === stageId);\n set({ selectedStage: stage || null });\n },\n\n createStage: async (funnelId: string, data: CreateStageInput<TEntity>) => {\n set({ isLoading: true, error: null });\n\n try {\n const stage = await apiClient.createStage<TEntity>(funnelId, data);\n\n // Update funnel with new stage\n set((state) => ({\n funnels: state.funnels.map((f) =>\n f.id === funnelId\n ? { ...f, stages: [...f.stages, stage] }\n : f\n ),\n selectedFunnel:\n state.selectedFunnel?.id === funnelId\n ? { ...state.selectedFunnel, stages: [...state.selectedFunnel.stages, stage] }\n : state.selectedFunnel,\n isLoading: false,\n }));\n\n return stage;\n } catch (error) {\n set({\n error: error as Error,\n isLoading: false,\n });\n throw error;\n }\n },\n\n updateStage: async (\n funnelId: string,\n stageId: string,\n data: Partial<UpdateStageInput<TEntity>>\n ) => {\n // Save state for rollback\n get()._saveRollbackState();\n\n // Optimistic update\n set((state) => ({\n funnels: state.funnels.map((f) =>\n f.id === funnelId\n ? {\n ...f,\n stages: f.stages.map((s) =>\n s.id === stageId ? ({ ...s, ...data } as FunnelStage<TEntity>) : s\n ),\n }\n : f\n ),\n selectedFunnel:\n state.selectedFunnel?.id === funnelId\n ? {\n ...state.selectedFunnel,\n stages: state.selectedFunnel.stages.map((s) =>\n s.id === stageId ? ({ ...s, ...data } as FunnelStage<TEntity>) : s\n ),\n }\n : state.selectedFunnel,\n selectedStage:\n state.selectedStage?.id === stageId\n ? ({ ...state.selectedStage, ...data } as FunnelStage<TEntity>)\n : state.selectedStage,\n isDirty: false,\n }));\n\n try {\n const updated = await apiClient.updateStage<TEntity>(\n funnelId,\n stageId,\n data\n );\n\n // Update with server response\n set((state) => ({\n funnels: state.funnels.map((f) =>\n f.id === funnelId\n ? {\n ...f,\n stages: f.stages.map((s) => (s.id === stageId ? updated : s)),\n }\n : f\n ),\n selectedFunnel:\n state.selectedFunnel?.id === funnelId\n ? {\n ...state.selectedFunnel,\n stages: state.selectedFunnel.stages.map((s) =>\n s.id === stageId ? updated : s\n ),\n }\n : state.selectedFunnel,\n selectedStage:\n state.selectedStage?.id === stageId ? updated : state.selectedStage,\n }));\n\n get()._clearRollback();\n return updated;\n } catch (error) {\n // Rollback optimistic update\n get()._rollback();\n set({ error: error as Error });\n throw error;\n }\n },\n\n deleteStage: async (funnelId: string, stageId: string) => {\n // Save state for rollback\n get()._saveRollbackState();\n\n // Optimistic update\n set((state) => ({\n funnels: state.funnels.map((f) =>\n f.id === funnelId\n ? {\n ...f,\n stages: f.stages.filter((s) => s.id !== stageId),\n }\n : f\n ),\n selectedFunnel:\n state.selectedFunnel?.id === funnelId\n ? {\n ...state.selectedFunnel,\n stages: state.selectedFunnel.stages.filter((s) => s.id !== stageId),\n }\n : state.selectedFunnel,\n selectedStage: state.selectedStage?.id === stageId ? null : state.selectedStage,\n }));\n\n try {\n await apiClient.deleteStage(funnelId, stageId);\n get()._clearRollback();\n } catch (error) {\n // Rollback optimistic update\n get()._rollback();\n set({ error: error as Error });\n throw error;\n }\n },\n\n reorderStages: async (funnelId: string, stageIds: string[]) => {\n const { selectedFunnel } = get();\n\n if (!selectedFunnel || selectedFunnel.id !== funnelId) {\n throw new Error('Funnel must be selected to reorder stages');\n }\n\n // Save state for rollback\n get()._saveRollbackState();\n\n // Optimistic update - reorder stages\n const reorderedStages = stageIds\n .map((id, index) => {\n const stage = selectedFunnel.stages.find((s) => s.id === id);\n return stage ? { ...stage, order: index } : null;\n })\n .filter((s): s is FunnelStage<TEntity> => s !== null);\n\n set((state) => ({\n funnels: state.funnels.map((f) =>\n f.id === funnelId ? { ...f, stages: reorderedStages } : f\n ),\n selectedFunnel: { ...selectedFunnel, stages: reorderedStages },\n isDirty: false,\n }));\n\n try {\n // Update each stage with new order\n await Promise.all(\n reorderedStages.map((stage) =>\n apiClient.updateStage(funnelId, stage.id, { order: stage.order })\n )\n );\n\n get()._clearRollback();\n } catch (error) {\n // Rollback optimistic update\n get()._rollback();\n set({ error: error as Error });\n throw error;\n }\n },\n\n // =========================================================================\n // Run Actions\n // =========================================================================\n\n runFunnel: async (id: string, options?) => {\n set({ isLoading: true, error: null });\n\n try {\n const run = await apiClient.runFunnel(id, options);\n\n // Add to runs list\n set((state) => ({\n runs: [run, ...state.runs],\n isLoading: false,\n }));\n\n return run;\n } catch (error) {\n set({\n error: error as Error,\n isLoading: false,\n });\n throw error;\n }\n },\n\n loadRuns: async (funnelId: string, filters?) => {\n set({ isLoading: true, error: null });\n\n try {\n const response = await apiClient.getFunnelRuns(funnelId, filters);\n\n set({\n runs: response.results,\n isLoading: false,\n });\n } catch (error) {\n set({\n error: error as Error,\n isLoading: false,\n });\n throw error;\n }\n },\n\n cancelRun: async (runId: string) => {\n set({ isLoading: true, error: null });\n\n try {\n const run = await apiClient.cancelFunnelRun(runId);\n\n // Update run in list\n set((state) => ({\n runs: state.runs.map((r) => (r.id === runId ? run : r)),\n isLoading: false,\n }));\n\n return run;\n } catch (error) {\n set({\n error: error as Error,\n isLoading: false,\n });\n throw error;\n }\n },\n\n // =========================================================================\n // UI State Actions\n // =========================================================================\n\n setDirty: (dirty: boolean) => {\n set({ isDirty: dirty });\n },\n\n clearError: () => {\n set({ error: null });\n },\n\n reset: () => {\n set(createInitialState<TEntity>());\n },\n\n // =========================================================================\n // Internal Actions (Optimistic Updates)\n // =========================================================================\n\n _saveRollbackState: () => {\n const { funnels, selectedFunnel } = get();\n set({\n rollbackState: {\n funnels: JSON.parse(JSON.stringify(funnels)) as Funnel<TEntity>[],\n selectedFunnel: selectedFunnel\n ? (JSON.parse(JSON.stringify(selectedFunnel)) as Funnel<TEntity>)\n : null,\n },\n });\n },\n\n _rollback: () => {\n const { rollbackState } = get();\n if (rollbackState) {\n set({\n funnels: rollbackState.funnels,\n selectedFunnel: rollbackState.selectedFunnel,\n rollbackState: null,\n });\n }\n },\n\n _clearRollback: () => {\n set({ rollbackState: null });\n },\n }));\n}\n"]}