@inspirer-dev/crm-dashboard 1.0.53 → 1.0.55

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.
@@ -2,7 +2,11 @@ import React, { forwardRef, useCallback, useEffect, useMemo, useState, useRef }
2
2
  import { ReactFlowProvider } from 'reactflow';
3
3
  import 'reactflow/dist/style.css';
4
4
  import { Box, Field, Flex, Typography, Badge } from '@strapi/design-system';
5
- import { useForm, unstable_useContentManagerContext as useContentManagerContext, useFetchClient } from '@strapi/strapi/admin';
5
+ import {
6
+ useForm,
7
+ unstable_useContentManagerContext as useContentManagerContext,
8
+ useFetchClient,
9
+ } from '@strapi/strapi/admin';
6
10
 
7
11
  import type { StepFlowBuilderProps, FlowStep, CampaignContext, EntrySegment } from './types';
8
12
  import type { ValidationResult } from './validation';
@@ -49,19 +53,16 @@ const StepFlowBuilderInner = forwardRef<HTMLDivElement, StepFlowBuilderProps>(
49
53
  },
50
54
  });
51
55
 
52
- const responseData = response?.data as { data?: { entrySegment?: { id: number; name: string } } } | undefined;
56
+ const responseData = response?.data as
57
+ | { data?: { entrySegment?: { id: number; name: string } } }
58
+ | undefined;
53
59
  const campaignData = responseData?.data;
54
- console.log('[StepFlowBuilder] Fetch response campaignData:', campaignData);
55
- console.log('[StepFlowBuilder] entrySegment from API:', campaignData?.entrySegment);
56
60
  if (campaignData?.entrySegment?.id) {
57
61
  const seg = {
58
62
  id: campaignData.entrySegment.id,
59
63
  name: campaignData.entrySegment.name || `Segment #${campaignData.entrySegment.id}`,
60
64
  };
61
- console.log('[StepFlowBuilder] Setting initialSegment:', seg);
62
65
  setInitialSegment(seg);
63
- } else {
64
- console.log('[StepFlowBuilder] No entrySegment.id found in API response');
65
66
  }
66
67
  } catch (err) {
67
68
  console.error('[StepFlowBuilder] Fetch error:', err);
@@ -77,51 +78,36 @@ const StepFlowBuilderInner = forwardRef<HTMLDivElement, StepFlowBuilderProps>(
77
78
  const values = form?.values as Record<string, unknown> | undefined;
78
79
  const entrySegmentValue = values?.entrySegment as ConnectDisconnect | SegmentItem | undefined;
79
80
 
80
- console.log('[StepFlowBuilder] currentSegment memo - form.values.entrySegment:', entrySegmentValue);
81
- console.log('[StepFlowBuilder] currentSegment memo - initialSegment:', initialSegment);
82
- console.log('[StepFlowBuilder] currentSegment memo - typeof entrySegmentValue:', typeof entrySegmentValue);
83
- if (entrySegmentValue) {
84
- console.log('[StepFlowBuilder] currentSegment memo - entrySegmentValue keys:', Object.keys(entrySegmentValue));
85
- }
86
-
87
81
  if (!entrySegmentValue) {
88
- console.log('[StepFlowBuilder] currentSegment memo - no entrySegmentValue, returning initialSegment');
89
82
  return initialSegment;
90
83
  }
91
84
 
92
85
  // Handle connect/disconnect structure (user changed the segment in UI)
93
86
  if ('connect' in entrySegmentValue || 'disconnect' in entrySegmentValue) {
94
- console.log('[StepFlowBuilder] currentSegment memo - detected connect/disconnect structure');
95
87
  const { connect = [], disconnect = [] } = entrySegmentValue as ConnectDisconnect;
96
88
 
97
89
  if (connect.length > 0) {
98
90
  const seg = connect[0];
99
- console.log('[StepFlowBuilder] currentSegment memo - returning from connect:', seg);
100
91
  return { id: seg.id!, name: seg.name || `Segment #${seg.id}` };
101
92
  }
102
93
 
103
94
  if (disconnect.length > 0 && initialSegment) {
104
95
  const disconnectedId = disconnect[0]?.id;
105
96
  if (disconnectedId === initialSegment.id) {
106
- console.log('[StepFlowBuilder] currentSegment memo - segment disconnected, returning null');
107
97
  return null;
108
98
  }
109
99
  }
110
100
 
111
- console.log('[StepFlowBuilder] currentSegment memo - connect/disconnect but falling back to initialSegment');
112
101
  return initialSegment;
113
102
  }
114
103
 
115
104
  // Handle direct segment object (loaded from server)
116
105
  const segmentObj = entrySegmentValue as SegmentItem;
117
- console.log('[StepFlowBuilder] currentSegment memo - treating as direct segment object:', segmentObj);
118
106
  if (segmentObj.id) {
119
107
  const result = { id: segmentObj.id, name: segmentObj.name || `Segment #${segmentObj.id}` };
120
- console.log('[StepFlowBuilder] currentSegment memo - returning direct segment:', result);
121
108
  return result;
122
109
  }
123
110
 
124
- console.log('[StepFlowBuilder] currentSegment memo - no id in segmentObj, returning initialSegment');
125
111
  return initialSegment;
126
112
  }, [form?.values, initialSegment]);
127
113
 
@@ -133,7 +119,6 @@ const StepFlowBuilderInner = forwardRef<HTMLDivElement, StepFlowBuilderProps>(
133
119
  triggerConfig: values.triggerConfig as CampaignContext['triggerConfig'],
134
120
  entrySegment: currentSegment,
135
121
  };
136
- console.log('[StepFlowBuilder] campaignContext:', ctx);
137
122
  return ctx;
138
123
  }, [form?.values, currentSegment]);
139
124
 
@@ -204,12 +189,18 @@ const StepFlowBuilderInner = forwardRef<HTMLDivElement, StepFlowBuilderProps>(
204
189
  <Field.Label>{displayLabel}</Field.Label>
205
190
  {validation && !validation.isValid && (
206
191
  <Badge backgroundColor="danger100" textColor="danger600">
207
- {validation.errors.length} {validation.errors.length === 1 ? 'ошибка' : validation.errors.length < 5 ? 'ошибки' : 'ошибок'}
192
+ {validation.errors.length}{' '}
193
+ {validation.errors.length === 1
194
+ ? 'ошибка'
195
+ : validation.errors.length < 5
196
+ ? 'ошибки'
197
+ : 'ошибок'}
208
198
  </Badge>
209
199
  )}
210
200
  {validation && validation.isValid && validation.warnings.length > 0 && (
211
201
  <Badge backgroundColor="warning100" textColor="warning600">
212
- {validation.warnings.length} {validation.warnings.length === 1 ? 'предупр.' : 'предупр.'}
202
+ {validation.warnings.length}{' '}
203
+ {validation.warnings.length === 1 ? 'предупр.' : 'предупр.'}
213
204
  </Badge>
214
205
  )}
215
206
  </Flex>
@@ -88,7 +88,6 @@ const EntryConfigInner: React.FC<EntryConfigProps & { theme: FlowThemeColors }>
88
88
  entrySegment,
89
89
  theme,
90
90
  }) => {
91
- console.log('[EntryConfig] Received entrySegment:', entrySegment);
92
91
  const config = parseTriggerConfig(triggerConfig);
93
92
 
94
93
  const renderTriggerInfo = () => {
@@ -235,11 +234,8 @@ const EntryConfigInner: React.FC<EntryConfigProps & { theme: FlowThemeColors }>
235
234
 
236
235
  const renderSegmentInfo = () => {
237
236
  const hasSegment = entrySegment && (entrySegment.id || entrySegment.name);
238
- const segmentDisplayName = entrySegment?.name || (entrySegment?.id ? `Segment #${entrySegment.id}` : null);
239
-
240
- console.log('[EntryConfig] renderSegmentInfo - entrySegment:', entrySegment);
241
- console.log('[EntryConfig] renderSegmentInfo - hasSegment:', hasSegment);
242
- console.log('[EntryConfig] renderSegmentInfo - segmentDisplayName:', segmentDisplayName);
237
+ const segmentDisplayName =
238
+ entrySegment?.name || (entrySegment?.id ? `Segment #${entrySegment.id}` : null);
243
239
 
244
240
  if (!hasSegment || !segmentDisplayName) {
245
241
  return (
@@ -16,7 +16,7 @@ const fetchABTestData = async (filters: StatsFilters): Promise<ABTestsResponse>
16
16
  params.set('campaignIds', filters.campaigns.join(','));
17
17
  }
18
18
 
19
- const backendUrl = getBackendUrl();
19
+ const backendUrl = await getBackendUrl();
20
20
  const res = await fetch(`${backendUrl}/api/crm/dashboard/ab-tests?${params}`);
21
21
 
22
22
  if (!res.ok) {
@@ -24,7 +24,7 @@ const fetchAntiSpamLogs = async (
24
24
  if (filters.dateFrom) params.set('from', filters.dateFrom.toISOString());
25
25
  if (filters.dateTo) params.set('to', filters.dateTo.toISOString());
26
26
 
27
- const backendUrl = getBackendUrl();
27
+ const backendUrl = await getBackendUrl();
28
28
  const res = await fetch(new URL(`/api/crm/anti-spam-logs?${params}`, backendUrl).toString());
29
29
 
30
30
  if (!res.ok) {
@@ -7,7 +7,7 @@ type CreateCampaignInput = Omit<Campaign, 'id' | 'documentId'>;
7
7
  type UpdateCampaignInput = Partial<Campaign> & { documentId: string };
8
8
 
9
9
  const createCampaign = async (input: CreateCampaignInput): Promise<Campaign> => {
10
- const backendUrl = getBackendUrl();
10
+ const backendUrl = await getBackendUrl();
11
11
  const res = await fetch(new URL('/api/crm/campaigns', backendUrl).toString(), {
12
12
  method: 'POST',
13
13
  headers: { 'Content-Type': 'application/json' },
@@ -23,7 +23,7 @@ const createCampaign = async (input: CreateCampaignInput): Promise<Campaign> =>
23
23
 
24
24
  const updateCampaign = async (input: UpdateCampaignInput): Promise<Campaign> => {
25
25
  const { documentId, ...data } = input;
26
- const backendUrl = getBackendUrl();
26
+ const backendUrl = await getBackendUrl();
27
27
  const res = await fetch(new URL(`/api/crm/campaigns/${documentId}`, backendUrl).toString(), {
28
28
  method: 'PUT',
29
29
  headers: { 'Content-Type': 'application/json' },
@@ -38,7 +38,7 @@ const updateCampaign = async (input: UpdateCampaignInput): Promise<Campaign> =>
38
38
  };
39
39
 
40
40
  const deleteCampaign = async (documentId: string): Promise<void> => {
41
- const backendUrl = getBackendUrl();
41
+ const backendUrl = await getBackendUrl();
42
42
  const res = await fetch(new URL(`/api/crm/campaigns/${documentId}`, backendUrl).toString(), {
43
43
  method: 'DELETE',
44
44
  });
@@ -5,7 +5,7 @@ import type { Campaign } from './types';
5
5
  import type { CrmSegment, CrmTemplate } from '../../types/crm';
6
6
 
7
7
  const fetchCampaigns = async (): Promise<Campaign[]> => {
8
- const backendUrl = getBackendUrl();
8
+ const backendUrl = await getBackendUrl();
9
9
  const res = await fetch(new URL('/api/crm/campaigns', backendUrl).toString());
10
10
 
11
11
  if (!res.ok) {
@@ -17,7 +17,7 @@ const fetchCampaigns = async (): Promise<Campaign[]> => {
17
17
  };
18
18
 
19
19
  const fetchSegments = async (): Promise<CrmSegment[]> => {
20
- const backendUrl = getBackendUrl();
20
+ const backendUrl = await getBackendUrl();
21
21
  const res = await fetch(new URL('/api/crm/segments', backendUrl).toString());
22
22
 
23
23
  if (!res.ok) {
@@ -29,7 +29,7 @@ const fetchSegments = async (): Promise<CrmSegment[]> => {
29
29
  };
30
30
 
31
31
  const fetchTemplates = async (): Promise<CrmTemplate[]> => {
32
- const backendUrl = getBackendUrl();
32
+ const backendUrl = await getBackendUrl();
33
33
  const res = await fetch(new URL('/api/crm/templates', backendUrl).toString());
34
34
 
35
35
  if (!res.ok) {
@@ -20,7 +20,7 @@ const fetchCohortData = async (filters: CohortFilters): Promise<CohortData> => {
20
20
  params.set('campaignIds', filters.campaigns.join(','));
21
21
  }
22
22
 
23
- const backendUrl = getBackendUrl();
23
+ const backendUrl = await getBackendUrl();
24
24
  const res = await fetch(`${backendUrl}/api/crm/dashboard/cohorts?${params}`);
25
25
 
26
26
  if (!res.ok) {
@@ -18,7 +18,7 @@ const fetchCrmLogs = async (filters: LogsFilters, page: number): Promise<CrmLogs
18
18
  if (filters.sentFrom) params.set('sentFrom', filters.sentFrom.toISOString());
19
19
  if (filters.sentTo) params.set('sentTo', filters.sentTo.toISOString());
20
20
 
21
- const backendUrl = getBackendUrl();
21
+ const backendUrl = await getBackendUrl();
22
22
  const res = await fetch(new URL(`/api/crm/logs?${params}`, backendUrl).toString());
23
23
 
24
24
  if (!res.ok) {
@@ -13,7 +13,7 @@ const fetchDashboardStats = async (filters: StatsFilters): Promise<DashboardStat
13
13
  if (filters.compareToPrevious) params.set('compareToPrevious', 'true');
14
14
  if (filters.granularity) params.set('granularity', filters.granularity);
15
15
 
16
- const backendUrl = getBackendUrl();
16
+ const backendUrl = await getBackendUrl();
17
17
  const res = await fetch(new URL(`/api/crm/dashboard/stats?${params}`, backendUrl).toString());
18
18
 
19
19
  if (!res.ok) {
@@ -4,7 +4,7 @@ import getBackendUrl from '../../utils/getBackendUrl';
4
4
  import type { FilterOptions } from './types';
5
5
 
6
6
  const fetchFilterOptions = async (): Promise<FilterOptions> => {
7
- const backendUrl = getBackendUrl();
7
+ const backendUrl = await getBackendUrl();
8
8
  const res = await fetch(new URL('/api/crm/filter-options', backendUrl).toString());
9
9
 
10
10
  if (!res.ok) {
@@ -10,7 +10,7 @@ const fetchRealtimeStats = async (campaignIds?: string[]): Promise<RealtimeStats
10
10
  params.set('campaignIds', campaignIds.join(','));
11
11
  }
12
12
 
13
- const backendUrl = getBackendUrl();
13
+ const backendUrl = await getBackendUrl();
14
14
  const url = params.toString()
15
15
  ? `${backendUrl}/api/crm/dashboard/realtime?${params}`
16
16
  : `${backendUrl}/api/crm/dashboard/realtime`;
@@ -12,7 +12,7 @@ const fetchSegmentStats = async (filters: StatsFilters): Promise<SegmentStats> =
12
12
  params.set('campaignIds', filters.campaigns.join(','));
13
13
  }
14
14
 
15
- const backendUrl = getBackendUrl();
15
+ const backendUrl = await getBackendUrl();
16
16
  const res = await fetch(`${backendUrl}/api/crm/dashboard/segments?${params}`);
17
17
 
18
18
  if (!res.ok) {
@@ -12,7 +12,7 @@ const fetchSendTimeData = async (filters: StatsFilters): Promise<SendTimeData> =
12
12
  params.set('campaignIds', filters.campaigns.join(','));
13
13
  }
14
14
 
15
- const backendUrl = getBackendUrl();
15
+ const backendUrl = await getBackendUrl();
16
16
  const res = await fetch(`${backendUrl}/api/crm/dashboard/send-times?${params}`);
17
17
 
18
18
  if (!res.ok) {
@@ -1,11 +1,25 @@
1
- const getBackendUrl = (): string => {
2
- const raw = process.env.STRAPI_ADMIN_API_URL || 'http://localhost:3100';
1
+ let cachedUrl: string | undefined;
3
2
 
4
- if (!/^https?:\/\//i.test(raw)) {
5
- return 'http://localhost:3100';
3
+ const getBackendUrl = async (): Promise<string> => {
4
+ if (cachedUrl !== undefined) {
5
+ return cachedUrl;
6
6
  }
7
7
 
8
- return raw.endsWith('/') ? raw.slice(0, -1) : raw;
8
+ try {
9
+ const res = await fetch('/api/crm-dashboard/config');
10
+ if (res.ok) {
11
+ const data = await res.json();
12
+ cachedUrl = data.apiUrl || '';
13
+ }
14
+ } catch (e) {
15
+ console.error('[CRM]: Failed to fetch config', e);
16
+ }
17
+
18
+ if (cachedUrl === undefined) {
19
+ cachedUrl = '';
20
+ }
21
+
22
+ return cachedUrl;
9
23
  };
10
24
 
11
- export default getBackendUrl;
25
+ export default getBackendUrl;
@@ -1926,7 +1926,6 @@ const EntryConfigInner = ({
1926
1926
  entrySegment,
1927
1927
  theme
1928
1928
  }) => {
1929
- console.log("[EntryConfig] Received entrySegment:", entrySegment);
1930
1929
  const config = parseTriggerConfig(triggerConfig);
1931
1930
  const renderTriggerInfo = () => {
1932
1931
  if (!config) {
@@ -2058,9 +2057,6 @@ const EntryConfigInner = ({
2058
2057
  const renderSegmentInfo = () => {
2059
2058
  const hasSegment = entrySegment && (entrySegment.id || entrySegment.name);
2060
2059
  const segmentDisplayName = entrySegment?.name || (entrySegment?.id ? `Segment #${entrySegment.id}` : null);
2061
- console.log("[EntryConfig] renderSegmentInfo - entrySegment:", entrySegment);
2062
- console.log("[EntryConfig] renderSegmentInfo - hasSegment:", hasSegment);
2063
- console.log("[EntryConfig] renderSegmentInfo - segmentDisplayName:", segmentDisplayName);
2064
2060
  if (!hasSegment || !segmentDisplayName) {
2065
2061
  return /* @__PURE__ */ jsxRuntime.jsx(
2066
2062
  "div",
@@ -3684,17 +3680,12 @@ const StepFlowBuilderInner = React.forwardRef(
3684
3680
  });
3685
3681
  const responseData = response?.data;
3686
3682
  const campaignData = responseData?.data;
3687
- console.log("[StepFlowBuilder] Fetch response campaignData:", campaignData);
3688
- console.log("[StepFlowBuilder] entrySegment from API:", campaignData?.entrySegment);
3689
3683
  if (campaignData?.entrySegment?.id) {
3690
3684
  const seg = {
3691
3685
  id: campaignData.entrySegment.id,
3692
3686
  name: campaignData.entrySegment.name || `Segment #${campaignData.entrySegment.id}`
3693
3687
  };
3694
- console.log("[StepFlowBuilder] Setting initialSegment:", seg);
3695
3688
  setInitialSegment(seg);
3696
- } else {
3697
- console.log("[StepFlowBuilder] No entrySegment.id found in API response");
3698
3689
  }
3699
3690
  } catch (err) {
3700
3691
  console.error("[StepFlowBuilder] Fetch error:", err);
@@ -3707,42 +3698,28 @@ const StepFlowBuilderInner = React.forwardRef(
3707
3698
  const currentSegment = React.useMemo(() => {
3708
3699
  const values = form?.values;
3709
3700
  const entrySegmentValue = values?.entrySegment;
3710
- console.log("[StepFlowBuilder] currentSegment memo - form.values.entrySegment:", entrySegmentValue);
3711
- console.log("[StepFlowBuilder] currentSegment memo - initialSegment:", initialSegment);
3712
- console.log("[StepFlowBuilder] currentSegment memo - typeof entrySegmentValue:", typeof entrySegmentValue);
3713
- if (entrySegmentValue) {
3714
- console.log("[StepFlowBuilder] currentSegment memo - entrySegmentValue keys:", Object.keys(entrySegmentValue));
3715
- }
3716
3701
  if (!entrySegmentValue) {
3717
- console.log("[StepFlowBuilder] currentSegment memo - no entrySegmentValue, returning initialSegment");
3718
3702
  return initialSegment;
3719
3703
  }
3720
3704
  if ("connect" in entrySegmentValue || "disconnect" in entrySegmentValue) {
3721
- console.log("[StepFlowBuilder] currentSegment memo - detected connect/disconnect structure");
3722
3705
  const { connect = [], disconnect = [] } = entrySegmentValue;
3723
3706
  if (connect.length > 0) {
3724
3707
  const seg = connect[0];
3725
- console.log("[StepFlowBuilder] currentSegment memo - returning from connect:", seg);
3726
3708
  return { id: seg.id, name: seg.name || `Segment #${seg.id}` };
3727
3709
  }
3728
3710
  if (disconnect.length > 0 && initialSegment) {
3729
3711
  const disconnectedId = disconnect[0]?.id;
3730
3712
  if (disconnectedId === initialSegment.id) {
3731
- console.log("[StepFlowBuilder] currentSegment memo - segment disconnected, returning null");
3732
3713
  return null;
3733
3714
  }
3734
3715
  }
3735
- console.log("[StepFlowBuilder] currentSegment memo - connect/disconnect but falling back to initialSegment");
3736
3716
  return initialSegment;
3737
3717
  }
3738
3718
  const segmentObj = entrySegmentValue;
3739
- console.log("[StepFlowBuilder] currentSegment memo - treating as direct segment object:", segmentObj);
3740
3719
  if (segmentObj.id) {
3741
3720
  const result = { id: segmentObj.id, name: segmentObj.name || `Segment #${segmentObj.id}` };
3742
- console.log("[StepFlowBuilder] currentSegment memo - returning direct segment:", result);
3743
3721
  return result;
3744
3722
  }
3745
- console.log("[StepFlowBuilder] currentSegment memo - no id in segmentObj, returning initialSegment");
3746
3723
  return initialSegment;
3747
3724
  }, [form?.values, initialSegment]);
3748
3725
  const campaignContext = React.useMemo(() => {
@@ -3752,7 +3729,6 @@ const StepFlowBuilderInner = React.forwardRef(
3752
3729
  triggerConfig: values.triggerConfig,
3753
3730
  entrySegment: currentSegment
3754
3731
  };
3755
- console.log("[StepFlowBuilder] campaignContext:", ctx);
3756
3732
  return ctx;
3757
3733
  }, [form?.values, currentSegment]);
3758
3734
  React.useEffect(() => {
@@ -41,15 +41,27 @@ const QueryProvider = ({ children }) => {
41
41
  /* @__PURE__ */ jsxRuntime.jsx(reactQueryDevtools.ReactQueryDevtools, { initialIsOpen: false })
42
42
  ] });
43
43
  };
44
- const getBackendUrl = () => {
45
- const raw = process.env.STRAPI_ADMIN_API_URL || "http://localhost:3100";
46
- if (!/^https?:\/\//i.test(raw)) {
47
- return "http://localhost:3100";
44
+ let cachedUrl;
45
+ const getBackendUrl = async () => {
46
+ if (cachedUrl !== void 0) {
47
+ return cachedUrl;
48
48
  }
49
- return raw.endsWith("/") ? raw.slice(0, -1) : raw;
49
+ try {
50
+ const res = await fetch("/api/crm-dashboard/config");
51
+ if (res.ok) {
52
+ const data = await res.json();
53
+ cachedUrl = data.apiUrl || "";
54
+ }
55
+ } catch (e) {
56
+ console.error("[CRM]: Failed to fetch config", e);
57
+ }
58
+ if (cachedUrl === void 0) {
59
+ cachedUrl = "";
60
+ }
61
+ return cachedUrl;
50
62
  };
51
63
  const fetchFilterOptions = async () => {
52
- const backendUrl = getBackendUrl();
64
+ const backendUrl = await getBackendUrl();
53
65
  const res = await fetch(new URL("/api/crm/filter-options", backendUrl).toString());
54
66
  if (!res.ok) {
55
67
  throw new Error("Failed to fetch filter options");
@@ -76,7 +88,7 @@ const fetchDashboardStats = async (filters) => {
76
88
  if (filters.segments.length > 0) params.set("segmentIds", filters.segments.join(","));
77
89
  if (filters.compareToPrevious) params.set("compareToPrevious", "true");
78
90
  if (filters.granularity) params.set("granularity", filters.granularity);
79
- const backendUrl = getBackendUrl();
91
+ const backendUrl = await getBackendUrl();
80
92
  const res = await fetch(new URL(`/api/crm/dashboard/stats?${params}`, backendUrl).toString());
81
93
  if (!res.ok) {
82
94
  throw new Error("Failed to fetch dashboard stats");
@@ -103,7 +115,7 @@ const fetchCrmLogs = async (filters, page) => {
103
115
  if (filters.scheduledTo) params.set("scheduledTo", filters.scheduledTo.toISOString());
104
116
  if (filters.sentFrom) params.set("sentFrom", filters.sentFrom.toISOString());
105
117
  if (filters.sentTo) params.set("sentTo", filters.sentTo.toISOString());
106
- const backendUrl = getBackendUrl();
118
+ const backendUrl = await getBackendUrl();
107
119
  const res = await fetch(new URL(`/api/crm/logs?${params}`, backendUrl).toString());
108
120
  if (!res.ok) {
109
121
  throw new Error("Failed to fetch CRM logs");
@@ -131,7 +143,7 @@ const fetchAntiSpamLogs = async (filters, page) => {
131
143
  if (filters.campaigns.length > 0) params.set("campaignIds", filters.campaigns.join(","));
132
144
  if (filters.dateFrom) params.set("from", filters.dateFrom.toISOString());
133
145
  if (filters.dateTo) params.set("to", filters.dateTo.toISOString());
134
- const backendUrl = getBackendUrl();
146
+ const backendUrl = await getBackendUrl();
135
147
  const res = await fetch(new URL(`/api/crm/anti-spam-logs?${params}`, backendUrl).toString());
136
148
  if (!res.ok) {
137
149
  throw new Error("Failed to fetch anti-spam logs");
@@ -156,7 +168,7 @@ const fetchRealtimeStats = async (campaignIds) => {
156
168
  if (campaignIds && campaignIds.length > 0) {
157
169
  params.set("campaignIds", campaignIds.join(","));
158
170
  }
159
- const backendUrl = getBackendUrl();
171
+ const backendUrl = await getBackendUrl();
160
172
  const url = params.toString() ? `${backendUrl}/api/crm/dashboard/realtime?${params}` : `${backendUrl}/api/crm/dashboard/realtime`;
161
173
  const res = await fetch(url);
162
174
  if (!res.ok) {
@@ -181,7 +193,7 @@ const fetchCohortData = async (filters) => {
181
193
  if (filters.campaigns && filters.campaigns.length > 0) {
182
194
  params.set("campaignIds", filters.campaigns.join(","));
183
195
  }
184
- const backendUrl = getBackendUrl();
196
+ const backendUrl = await getBackendUrl();
185
197
  const res = await fetch(`${backendUrl}/api/crm/dashboard/cohorts?${params}`);
186
198
  if (!res.ok) {
187
199
  throw new Error("Failed to fetch cohort data");
@@ -207,7 +219,7 @@ const fetchSegmentStats = async (filters) => {
207
219
  if (filters.campaigns && filters.campaigns.length > 0) {
208
220
  params.set("campaignIds", filters.campaigns.join(","));
209
221
  }
210
- const backendUrl = getBackendUrl();
222
+ const backendUrl = await getBackendUrl();
211
223
  const res = await fetch(`${backendUrl}/api/crm/dashboard/segments?${params}`);
212
224
  if (!res.ok) {
213
225
  throw new Error("Failed to fetch segment stats");
@@ -228,7 +240,7 @@ const fetchSendTimeData = async (filters) => {
228
240
  if (filters.campaigns && filters.campaigns.length > 0) {
229
241
  params.set("campaignIds", filters.campaigns.join(","));
230
242
  }
231
- const backendUrl = getBackendUrl();
243
+ const backendUrl = await getBackendUrl();
232
244
  const res = await fetch(`${backendUrl}/api/crm/dashboard/send-times?${params}`);
233
245
  if (!res.ok) {
234
246
  throw new Error("Failed to fetch send time data");
@@ -249,7 +261,7 @@ const fetchABTestData = async (filters) => {
249
261
  if (filters.campaigns && filters.campaigns.length > 0) {
250
262
  params.set("campaignIds", filters.campaigns.join(","));
251
263
  }
252
- const backendUrl = getBackendUrl();
264
+ const backendUrl = await getBackendUrl();
253
265
  const res = await fetch(`${backendUrl}/api/crm/dashboard/ab-tests?${params}`);
254
266
  if (!res.ok) {
255
267
  throw new Error("Failed to fetch A/B test data");
@@ -1921,7 +1921,6 @@ const EntryConfigInner = ({
1921
1921
  entrySegment,
1922
1922
  theme
1923
1923
  }) => {
1924
- console.log("[EntryConfig] Received entrySegment:", entrySegment);
1925
1924
  const config = parseTriggerConfig(triggerConfig);
1926
1925
  const renderTriggerInfo = () => {
1927
1926
  if (!config) {
@@ -2053,9 +2052,6 @@ const EntryConfigInner = ({
2053
2052
  const renderSegmentInfo = () => {
2054
2053
  const hasSegment = entrySegment && (entrySegment.id || entrySegment.name);
2055
2054
  const segmentDisplayName = entrySegment?.name || (entrySegment?.id ? `Segment #${entrySegment.id}` : null);
2056
- console.log("[EntryConfig] renderSegmentInfo - entrySegment:", entrySegment);
2057
- console.log("[EntryConfig] renderSegmentInfo - hasSegment:", hasSegment);
2058
- console.log("[EntryConfig] renderSegmentInfo - segmentDisplayName:", segmentDisplayName);
2059
2055
  if (!hasSegment || !segmentDisplayName) {
2060
2056
  return /* @__PURE__ */ jsx(
2061
2057
  "div",
@@ -3679,17 +3675,12 @@ const StepFlowBuilderInner = forwardRef(
3679
3675
  });
3680
3676
  const responseData = response?.data;
3681
3677
  const campaignData = responseData?.data;
3682
- console.log("[StepFlowBuilder] Fetch response campaignData:", campaignData);
3683
- console.log("[StepFlowBuilder] entrySegment from API:", campaignData?.entrySegment);
3684
3678
  if (campaignData?.entrySegment?.id) {
3685
3679
  const seg = {
3686
3680
  id: campaignData.entrySegment.id,
3687
3681
  name: campaignData.entrySegment.name || `Segment #${campaignData.entrySegment.id}`
3688
3682
  };
3689
- console.log("[StepFlowBuilder] Setting initialSegment:", seg);
3690
3683
  setInitialSegment(seg);
3691
- } else {
3692
- console.log("[StepFlowBuilder] No entrySegment.id found in API response");
3693
3684
  }
3694
3685
  } catch (err) {
3695
3686
  console.error("[StepFlowBuilder] Fetch error:", err);
@@ -3702,42 +3693,28 @@ const StepFlowBuilderInner = forwardRef(
3702
3693
  const currentSegment = useMemo(() => {
3703
3694
  const values = form?.values;
3704
3695
  const entrySegmentValue = values?.entrySegment;
3705
- console.log("[StepFlowBuilder] currentSegment memo - form.values.entrySegment:", entrySegmentValue);
3706
- console.log("[StepFlowBuilder] currentSegment memo - initialSegment:", initialSegment);
3707
- console.log("[StepFlowBuilder] currentSegment memo - typeof entrySegmentValue:", typeof entrySegmentValue);
3708
- if (entrySegmentValue) {
3709
- console.log("[StepFlowBuilder] currentSegment memo - entrySegmentValue keys:", Object.keys(entrySegmentValue));
3710
- }
3711
3696
  if (!entrySegmentValue) {
3712
- console.log("[StepFlowBuilder] currentSegment memo - no entrySegmentValue, returning initialSegment");
3713
3697
  return initialSegment;
3714
3698
  }
3715
3699
  if ("connect" in entrySegmentValue || "disconnect" in entrySegmentValue) {
3716
- console.log("[StepFlowBuilder] currentSegment memo - detected connect/disconnect structure");
3717
3700
  const { connect = [], disconnect = [] } = entrySegmentValue;
3718
3701
  if (connect.length > 0) {
3719
3702
  const seg = connect[0];
3720
- console.log("[StepFlowBuilder] currentSegment memo - returning from connect:", seg);
3721
3703
  return { id: seg.id, name: seg.name || `Segment #${seg.id}` };
3722
3704
  }
3723
3705
  if (disconnect.length > 0 && initialSegment) {
3724
3706
  const disconnectedId = disconnect[0]?.id;
3725
3707
  if (disconnectedId === initialSegment.id) {
3726
- console.log("[StepFlowBuilder] currentSegment memo - segment disconnected, returning null");
3727
3708
  return null;
3728
3709
  }
3729
3710
  }
3730
- console.log("[StepFlowBuilder] currentSegment memo - connect/disconnect but falling back to initialSegment");
3731
3711
  return initialSegment;
3732
3712
  }
3733
3713
  const segmentObj = entrySegmentValue;
3734
- console.log("[StepFlowBuilder] currentSegment memo - treating as direct segment object:", segmentObj);
3735
3714
  if (segmentObj.id) {
3736
3715
  const result = { id: segmentObj.id, name: segmentObj.name || `Segment #${segmentObj.id}` };
3737
- console.log("[StepFlowBuilder] currentSegment memo - returning direct segment:", result);
3738
3716
  return result;
3739
3717
  }
3740
- console.log("[StepFlowBuilder] currentSegment memo - no id in segmentObj, returning initialSegment");
3741
3718
  return initialSegment;
3742
3719
  }, [form?.values, initialSegment]);
3743
3720
  const campaignContext = useMemo(() => {
@@ -3747,7 +3724,6 @@ const StepFlowBuilderInner = forwardRef(
3747
3724
  triggerConfig: values.triggerConfig,
3748
3725
  entrySegment: currentSegment
3749
3726
  };
3750
- console.log("[StepFlowBuilder] campaignContext:", ctx);
3751
3727
  return ctx;
3752
3728
  }, [form?.values, currentSegment]);
3753
3729
  useEffect(() => {
@@ -37,15 +37,27 @@ const QueryProvider = ({ children }) => {
37
37
  /* @__PURE__ */ jsx(ReactQueryDevtools, { initialIsOpen: false })
38
38
  ] });
39
39
  };
40
- const getBackendUrl = () => {
41
- const raw = process.env.STRAPI_ADMIN_API_URL || "http://localhost:3100";
42
- if (!/^https?:\/\//i.test(raw)) {
43
- return "http://localhost:3100";
40
+ let cachedUrl;
41
+ const getBackendUrl = async () => {
42
+ if (cachedUrl !== void 0) {
43
+ return cachedUrl;
44
44
  }
45
- return raw.endsWith("/") ? raw.slice(0, -1) : raw;
45
+ try {
46
+ const res = await fetch("/api/crm-dashboard/config");
47
+ if (res.ok) {
48
+ const data = await res.json();
49
+ cachedUrl = data.apiUrl || "";
50
+ }
51
+ } catch (e) {
52
+ console.error("[CRM]: Failed to fetch config", e);
53
+ }
54
+ if (cachedUrl === void 0) {
55
+ cachedUrl = "";
56
+ }
57
+ return cachedUrl;
46
58
  };
47
59
  const fetchFilterOptions = async () => {
48
- const backendUrl = getBackendUrl();
60
+ const backendUrl = await getBackendUrl();
49
61
  const res = await fetch(new URL("/api/crm/filter-options", backendUrl).toString());
50
62
  if (!res.ok) {
51
63
  throw new Error("Failed to fetch filter options");
@@ -72,7 +84,7 @@ const fetchDashboardStats = async (filters) => {
72
84
  if (filters.segments.length > 0) params.set("segmentIds", filters.segments.join(","));
73
85
  if (filters.compareToPrevious) params.set("compareToPrevious", "true");
74
86
  if (filters.granularity) params.set("granularity", filters.granularity);
75
- const backendUrl = getBackendUrl();
87
+ const backendUrl = await getBackendUrl();
76
88
  const res = await fetch(new URL(`/api/crm/dashboard/stats?${params}`, backendUrl).toString());
77
89
  if (!res.ok) {
78
90
  throw new Error("Failed to fetch dashboard stats");
@@ -99,7 +111,7 @@ const fetchCrmLogs = async (filters, page) => {
99
111
  if (filters.scheduledTo) params.set("scheduledTo", filters.scheduledTo.toISOString());
100
112
  if (filters.sentFrom) params.set("sentFrom", filters.sentFrom.toISOString());
101
113
  if (filters.sentTo) params.set("sentTo", filters.sentTo.toISOString());
102
- const backendUrl = getBackendUrl();
114
+ const backendUrl = await getBackendUrl();
103
115
  const res = await fetch(new URL(`/api/crm/logs?${params}`, backendUrl).toString());
104
116
  if (!res.ok) {
105
117
  throw new Error("Failed to fetch CRM logs");
@@ -127,7 +139,7 @@ const fetchAntiSpamLogs = async (filters, page) => {
127
139
  if (filters.campaigns.length > 0) params.set("campaignIds", filters.campaigns.join(","));
128
140
  if (filters.dateFrom) params.set("from", filters.dateFrom.toISOString());
129
141
  if (filters.dateTo) params.set("to", filters.dateTo.toISOString());
130
- const backendUrl = getBackendUrl();
142
+ const backendUrl = await getBackendUrl();
131
143
  const res = await fetch(new URL(`/api/crm/anti-spam-logs?${params}`, backendUrl).toString());
132
144
  if (!res.ok) {
133
145
  throw new Error("Failed to fetch anti-spam logs");
@@ -152,7 +164,7 @@ const fetchRealtimeStats = async (campaignIds) => {
152
164
  if (campaignIds && campaignIds.length > 0) {
153
165
  params.set("campaignIds", campaignIds.join(","));
154
166
  }
155
- const backendUrl = getBackendUrl();
167
+ const backendUrl = await getBackendUrl();
156
168
  const url = params.toString() ? `${backendUrl}/api/crm/dashboard/realtime?${params}` : `${backendUrl}/api/crm/dashboard/realtime`;
157
169
  const res = await fetch(url);
158
170
  if (!res.ok) {
@@ -177,7 +189,7 @@ const fetchCohortData = async (filters) => {
177
189
  if (filters.campaigns && filters.campaigns.length > 0) {
178
190
  params.set("campaignIds", filters.campaigns.join(","));
179
191
  }
180
- const backendUrl = getBackendUrl();
192
+ const backendUrl = await getBackendUrl();
181
193
  const res = await fetch(`${backendUrl}/api/crm/dashboard/cohorts?${params}`);
182
194
  if (!res.ok) {
183
195
  throw new Error("Failed to fetch cohort data");
@@ -203,7 +215,7 @@ const fetchSegmentStats = async (filters) => {
203
215
  if (filters.campaigns && filters.campaigns.length > 0) {
204
216
  params.set("campaignIds", filters.campaigns.join(","));
205
217
  }
206
- const backendUrl = getBackendUrl();
218
+ const backendUrl = await getBackendUrl();
207
219
  const res = await fetch(`${backendUrl}/api/crm/dashboard/segments?${params}`);
208
220
  if (!res.ok) {
209
221
  throw new Error("Failed to fetch segment stats");
@@ -224,7 +236,7 @@ const fetchSendTimeData = async (filters) => {
224
236
  if (filters.campaigns && filters.campaigns.length > 0) {
225
237
  params.set("campaignIds", filters.campaigns.join(","));
226
238
  }
227
- const backendUrl = getBackendUrl();
239
+ const backendUrl = await getBackendUrl();
228
240
  const res = await fetch(`${backendUrl}/api/crm/dashboard/send-times?${params}`);
229
241
  if (!res.ok) {
230
242
  throw new Error("Failed to fetch send time data");
@@ -245,7 +257,7 @@ const fetchABTestData = async (filters) => {
245
257
  if (filters.campaigns && filters.campaigns.length > 0) {
246
258
  params.set("campaignIds", filters.campaigns.join(","));
247
259
  }
248
- const backendUrl = getBackendUrl();
260
+ const backendUrl = await getBackendUrl();
249
261
  const res = await fetch(`${backendUrl}/api/crm/dashboard/ab-tests?${params}`);
250
262
  if (!res.ok) {
251
263
  throw new Error("Failed to fetch A/B test data");
@@ -131,7 +131,7 @@ const index = {
131
131
  components: {
132
132
  Input: async () => Promise.resolve().then(() => require(
133
133
  /* webpackChunkName: "crm-step-flow-builder" */
134
- "../_chunks/index-C_zGO7mZ.js"
134
+ "../_chunks/index--YXTcU9K.js"
135
135
  ))
136
136
  },
137
137
  options: {
@@ -149,7 +149,7 @@ const index = {
149
149
  Component: async () => {
150
150
  const component = await Promise.resolve().then(() => require(
151
151
  /* webpackChunkName: "crm-dashboard-page" */
152
- "../_chunks/index-wE233MhG.js"
152
+ "../_chunks/index-CCjUeHQa.js"
153
153
  ));
154
154
  return component;
155
155
  },
@@ -130,7 +130,7 @@ const index = {
130
130
  components: {
131
131
  Input: async () => import(
132
132
  /* webpackChunkName: "crm-step-flow-builder" */
133
- "../_chunks/index-6qhuoAbM.mjs"
133
+ "../_chunks/index-CLXsV-bm.mjs"
134
134
  )
135
135
  },
136
136
  options: {
@@ -148,7 +148,7 @@ const index = {
148
148
  Component: async () => {
149
149
  const component = await import(
150
150
  /* webpackChunkName: "crm-dashboard-page" */
151
- "../_chunks/index-DLPqbGjQ.mjs"
151
+ "../_chunks/index-bOPRBiEo.mjs"
152
152
  );
153
153
  return component;
154
154
  },
@@ -40,6 +40,10 @@ const controller = ({ strapi }) => ({
40
40
  async index(ctx) {
41
41
  ctx.body = await strapi.plugin("crm-dashboard").service("service").getWelcomeMessage();
42
42
  },
43
+ async getConfig(ctx) {
44
+ const apiUrl = strapi.config.get("server.apiUrl");
45
+ ctx.body = { apiUrl: apiUrl || "" };
46
+ },
43
47
  async getLogs(ctx) {
44
48
  ctx.body = await strapi.plugin("crm-dashboard").service("service").getLogs(ctx.query);
45
49
  },
@@ -59,6 +63,14 @@ const routes = [
59
63
  policies: []
60
64
  }
61
65
  },
66
+ {
67
+ method: "GET",
68
+ path: "/config",
69
+ handler: "controller.getConfig",
70
+ config: {
71
+ policies: []
72
+ }
73
+ },
62
74
  {
63
75
  method: "GET",
64
76
  path: "/logs",
@@ -39,6 +39,10 @@ const controller = ({ strapi }) => ({
39
39
  async index(ctx) {
40
40
  ctx.body = await strapi.plugin("crm-dashboard").service("service").getWelcomeMessage();
41
41
  },
42
+ async getConfig(ctx) {
43
+ const apiUrl = strapi.config.get("server.apiUrl");
44
+ ctx.body = { apiUrl: apiUrl || "" };
45
+ },
42
46
  async getLogs(ctx) {
43
47
  ctx.body = await strapi.plugin("crm-dashboard").service("service").getLogs(ctx.query);
44
48
  },
@@ -58,6 +62,14 @@ const routes = [
58
62
  policies: []
59
63
  }
60
64
  },
65
+ {
66
+ method: "GET",
67
+ path: "/config",
68
+ handler: "controller.getConfig",
69
+ config: {
70
+ policies: []
71
+ }
72
+ },
61
73
  {
62
74
  method: "GET",
63
75
  path: "/logs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inspirer-dev/crm-dashboard",
3
- "version": "1.0.53",
3
+ "version": "1.0.55",
4
4
  "description": "CRM Dashboard and Tools",
5
5
  "strapi": {
6
6
  "name": "crm-dashboard",
@@ -8,6 +8,11 @@ const controller = ({ strapi }: { strapi: Core.Strapi }) => ({
8
8
  .getWelcomeMessage();
9
9
  },
10
10
 
11
+ async getConfig(ctx: any) {
12
+ const apiUrl = strapi.config.get('server.apiUrl') as string;
13
+ ctx.body = { apiUrl: apiUrl || '' };
14
+ },
15
+
11
16
  async getLogs(ctx: any) {
12
17
  ctx.body = await strapi
13
18
  .plugin('crm-dashboard')
@@ -7,6 +7,14 @@ export default [
7
7
  policies: [],
8
8
  },
9
9
  },
10
+ {
11
+ method: 'GET',
12
+ path: '/config',
13
+ handler: 'controller.getConfig',
14
+ config: {
15
+ policies: [],
16
+ },
17
+ },
10
18
  {
11
19
  method: 'GET',
12
20
  path: '/logs',