@clianta/sdk 1.1.1 → 1.2.0

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.
package/dist/react.cjs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Clianta SDK v1.1.1
2
+ * Clianta SDK v1.2.0
3
3
  * (c) 2026 Clianta
4
4
  * Released under the MIT License.
5
5
  */
@@ -13,7 +13,7 @@ var react = require('react');
13
13
  * @see SDK_VERSION in core/config.ts
14
14
  */
15
15
  /** SDK Version */
16
- const SDK_VERSION = '1.1.0';
16
+ const SDK_VERSION = '1.2.0';
17
17
  /** Default API endpoint based on environment */
18
18
  const getDefaultApiEndpoint = () => {
19
19
  if (typeof window === 'undefined')
@@ -577,14 +577,20 @@ function getDeviceInfo() {
577
577
  * @see SDK_VERSION in core/config.ts
578
578
  */
579
579
  const MAX_QUEUE_SIZE = 1000;
580
+ /** Rate limit: max events per window */
581
+ const RATE_LIMIT_MAX_EVENTS = 100;
582
+ /** Rate limit window in ms (1 minute) */
583
+ const RATE_LIMIT_WINDOW_MS = 60000;
580
584
  /**
581
- * Event queue with batching, persistence, and auto-flush
585
+ * Event queue with batching, persistence, rate limiting, and auto-flush
582
586
  */
583
587
  class EventQueue {
584
588
  constructor(transport, config = {}) {
585
589
  this.queue = [];
586
590
  this.flushTimer = null;
587
591
  this.isFlushing = false;
592
+ /** Rate limiting: timestamps of recent events */
593
+ this.eventTimestamps = [];
588
594
  this.transport = transport;
589
595
  this.config = {
590
596
  batchSize: config.batchSize ?? 10,
@@ -603,6 +609,11 @@ class EventQueue {
603
609
  * Add an event to the queue
604
610
  */
605
611
  push(event) {
612
+ // Rate limiting check
613
+ if (!this.checkRateLimit()) {
614
+ logger.warn('Rate limit exceeded, event dropped:', event.eventName);
615
+ return;
616
+ }
606
617
  // Don't exceed max queue size
607
618
  if (this.queue.length >= this.config.maxQueueSize) {
608
619
  logger.warn('Queue full, dropping oldest event');
@@ -615,6 +626,22 @@ class EventQueue {
615
626
  this.flush();
616
627
  }
617
628
  }
629
+ /**
630
+ * Check and enforce rate limiting
631
+ * @returns true if event is allowed, false if rate limited
632
+ */
633
+ checkRateLimit() {
634
+ const now = Date.now();
635
+ // Remove timestamps outside the window
636
+ this.eventTimestamps = this.eventTimestamps.filter(ts => now - ts < RATE_LIMIT_WINDOW_MS);
637
+ // Check if under limit
638
+ if (this.eventTimestamps.length >= RATE_LIMIT_MAX_EVENTS) {
639
+ return false;
640
+ }
641
+ // Record this event
642
+ this.eventTimestamps.push(now);
643
+ return true;
644
+ }
618
645
  /**
619
646
  * Flush the queue (send all events)
620
647
  */
@@ -623,9 +650,10 @@ class EventQueue {
623
650
  return;
624
651
  }
625
652
  this.isFlushing = true;
653
+ // Atomically take snapshot of current queue length to avoid race condition
654
+ const count = this.queue.length;
655
+ const events = this.queue.splice(0, count);
626
656
  try {
627
- // Take all events from queue
628
- const events = this.queue.splice(0, this.queue.length);
629
657
  logger.debug(`Flushing ${events.length} events`);
630
658
  // Clear persisted queue
631
659
  this.persistQueue([]);
@@ -783,6 +811,9 @@ class PageViewPlugin extends BasePlugin {
783
811
  constructor() {
784
812
  super(...arguments);
785
813
  this.name = 'pageView';
814
+ this.originalPushState = null;
815
+ this.originalReplaceState = null;
816
+ this.popstateHandler = null;
786
817
  }
787
818
  init(tracker) {
788
819
  super.init(tracker);
@@ -790,22 +821,40 @@ class PageViewPlugin extends BasePlugin {
790
821
  this.trackPageView();
791
822
  // Track SPA navigation (History API)
792
823
  if (typeof window !== 'undefined') {
824
+ // Store originals for cleanup
825
+ this.originalPushState = history.pushState;
826
+ this.originalReplaceState = history.replaceState;
793
827
  // Intercept pushState and replaceState
794
- const originalPushState = history.pushState;
795
- const originalReplaceState = history.replaceState;
796
- history.pushState = (...args) => {
797
- originalPushState.apply(history, args);
798
- this.trackPageView();
828
+ const self = this;
829
+ history.pushState = function (...args) {
830
+ self.originalPushState.apply(history, args);
831
+ self.trackPageView();
799
832
  };
800
- history.replaceState = (...args) => {
801
- originalReplaceState.apply(history, args);
802
- this.trackPageView();
833
+ history.replaceState = function (...args) {
834
+ self.originalReplaceState.apply(history, args);
835
+ self.trackPageView();
803
836
  };
804
837
  // Handle back/forward navigation
805
- window.addEventListener('popstate', () => {
806
- this.trackPageView();
807
- });
838
+ this.popstateHandler = () => this.trackPageView();
839
+ window.addEventListener('popstate', this.popstateHandler);
840
+ }
841
+ }
842
+ destroy() {
843
+ // Restore original history methods
844
+ if (this.originalPushState) {
845
+ history.pushState = this.originalPushState;
846
+ this.originalPushState = null;
847
+ }
848
+ if (this.originalReplaceState) {
849
+ history.replaceState = this.originalReplaceState;
850
+ this.originalReplaceState = null;
851
+ }
852
+ // Remove popstate listener
853
+ if (this.popstateHandler && typeof window !== 'undefined') {
854
+ window.removeEventListener('popstate', this.popstateHandler);
855
+ this.popstateHandler = null;
808
856
  }
857
+ super.destroy();
809
858
  }
810
859
  trackPageView() {
811
860
  if (typeof window === 'undefined' || typeof document === 'undefined')
@@ -868,7 +917,11 @@ class ScrollPlugin extends BasePlugin {
868
917
  const windowHeight = window.innerHeight;
869
918
  const documentHeight = document.documentElement.scrollHeight;
870
919
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
871
- const scrollPercent = Math.floor((scrollTop / (documentHeight - windowHeight)) * 100);
920
+ const scrollableHeight = documentHeight - windowHeight;
921
+ // Guard against divide-by-zero on short pages
922
+ if (scrollableHeight <= 0)
923
+ return;
924
+ const scrollPercent = Math.floor((scrollTop / scrollableHeight) * 100);
872
925
  // Clamp to valid range
873
926
  const clampedPercent = Math.max(0, Math.min(100, scrollPercent));
874
927
  // Update max scroll depth
@@ -1287,20 +1340,41 @@ class PerformancePlugin extends BasePlugin {
1287
1340
  trackPerformance() {
1288
1341
  if (typeof performance === 'undefined')
1289
1342
  return;
1290
- // Use Navigation Timing API
1291
- const timing = performance.timing;
1292
- if (!timing)
1293
- return;
1294
- const loadTime = timing.loadEventEnd - timing.navigationStart;
1295
- const domReady = timing.domContentLoadedEventEnd - timing.navigationStart;
1296
- const ttfb = timing.responseStart - timing.navigationStart;
1297
- const domInteractive = timing.domInteractive - timing.navigationStart;
1298
- this.track('performance', 'Page Performance', {
1299
- loadTime,
1300
- domReady,
1301
- ttfb, // Time to First Byte
1302
- domInteractive,
1303
- });
1343
+ // Use modern Navigation Timing API (PerformanceNavigationTiming)
1344
+ const entries = performance.getEntriesByType('navigation');
1345
+ if (entries.length > 0) {
1346
+ const navTiming = entries[0];
1347
+ const loadTime = Math.round(navTiming.loadEventEnd - navTiming.startTime);
1348
+ const domReady = Math.round(navTiming.domContentLoadedEventEnd - navTiming.startTime);
1349
+ const ttfb = Math.round(navTiming.responseStart - navTiming.requestStart);
1350
+ const domInteractive = Math.round(navTiming.domInteractive - navTiming.startTime);
1351
+ this.track('performance', 'Page Performance', {
1352
+ loadTime,
1353
+ domReady,
1354
+ ttfb, // Time to First Byte
1355
+ domInteractive,
1356
+ // Additional modern metrics
1357
+ dns: Math.round(navTiming.domainLookupEnd - navTiming.domainLookupStart),
1358
+ connection: Math.round(navTiming.connectEnd - navTiming.connectStart),
1359
+ transferSize: navTiming.transferSize,
1360
+ });
1361
+ }
1362
+ else {
1363
+ // Fallback for older browsers using deprecated API
1364
+ const timing = performance.timing;
1365
+ if (!timing)
1366
+ return;
1367
+ const loadTime = timing.loadEventEnd - timing.navigationStart;
1368
+ const domReady = timing.domContentLoadedEventEnd - timing.navigationStart;
1369
+ const ttfb = timing.responseStart - timing.navigationStart;
1370
+ const domInteractive = timing.domInteractive - timing.navigationStart;
1371
+ this.track('performance', 'Page Performance', {
1372
+ loadTime,
1373
+ domReady,
1374
+ ttfb,
1375
+ domInteractive,
1376
+ });
1377
+ }
1304
1378
  // Track Web Vitals if available
1305
1379
  this.trackWebVitals();
1306
1380
  }
@@ -1577,8 +1651,8 @@ class PopupFormsPlugin extends BasePlugin {
1577
1651
  opacity: 0;
1578
1652
  transition: all 0.3s ease;
1579
1653
  `;
1580
- // Build form HTML
1581
- container.innerHTML = this.buildFormHTML(form);
1654
+ // Build form using safe DOM APIs (no innerHTML for user content)
1655
+ this.buildFormDOM(form, container);
1582
1656
  overlay.appendChild(container);
1583
1657
  document.body.appendChild(overlay);
1584
1658
  // Animate in
@@ -1590,6 +1664,131 @@ class PopupFormsPlugin extends BasePlugin {
1590
1664
  // Setup event listeners
1591
1665
  this.setupFormEvents(form, overlay, container);
1592
1666
  }
1667
+ /**
1668
+ * Escape HTML to prevent XSS - used only for static structure
1669
+ */
1670
+ escapeHTML(str) {
1671
+ const div = document.createElement('div');
1672
+ div.textContent = str;
1673
+ return div.innerHTML;
1674
+ }
1675
+ /**
1676
+ * Build form using safe DOM APIs (prevents XSS)
1677
+ */
1678
+ buildFormDOM(form, container) {
1679
+ const style = form.style || {};
1680
+ const primaryColor = style.primaryColor || '#10B981';
1681
+ const textColor = style.textColor || '#18181B';
1682
+ // Close button
1683
+ const closeBtn = document.createElement('button');
1684
+ closeBtn.id = 'clianta-form-close';
1685
+ closeBtn.style.cssText = `
1686
+ position: absolute;
1687
+ top: 12px;
1688
+ right: 12px;
1689
+ background: none;
1690
+ border: none;
1691
+ font-size: 20px;
1692
+ cursor: pointer;
1693
+ color: #71717A;
1694
+ padding: 4px;
1695
+ `;
1696
+ closeBtn.textContent = '×';
1697
+ container.appendChild(closeBtn);
1698
+ // Headline
1699
+ const headline = document.createElement('h2');
1700
+ headline.style.cssText = `font-size: 20px; font-weight: 700; margin-bottom: 8px; color: ${this.escapeHTML(textColor)};`;
1701
+ headline.textContent = form.headline || 'Stay in touch';
1702
+ container.appendChild(headline);
1703
+ // Subheadline
1704
+ const subheadline = document.createElement('p');
1705
+ subheadline.style.cssText = 'font-size: 14px; color: #71717A; margin-bottom: 16px;';
1706
+ subheadline.textContent = form.subheadline || 'Get the latest updates';
1707
+ container.appendChild(subheadline);
1708
+ // Form element
1709
+ const formElement = document.createElement('form');
1710
+ formElement.id = 'clianta-form-element';
1711
+ // Build fields
1712
+ form.fields.forEach(field => {
1713
+ const fieldWrapper = document.createElement('div');
1714
+ fieldWrapper.style.marginBottom = '12px';
1715
+ if (field.type === 'checkbox') {
1716
+ // Checkbox layout
1717
+ const label = document.createElement('label');
1718
+ label.style.cssText = `display: flex; align-items: center; gap: 8px; font-size: 14px; color: ${this.escapeHTML(textColor)}; cursor: pointer;`;
1719
+ const input = document.createElement('input');
1720
+ input.type = 'checkbox';
1721
+ input.name = field.name;
1722
+ if (field.required)
1723
+ input.required = true;
1724
+ input.style.cssText = 'width: 16px; height: 16px;';
1725
+ label.appendChild(input);
1726
+ const labelText = document.createTextNode(field.label + ' ');
1727
+ label.appendChild(labelText);
1728
+ if (field.required) {
1729
+ const requiredMark = document.createElement('span');
1730
+ requiredMark.style.color = '#EF4444';
1731
+ requiredMark.textContent = '*';
1732
+ label.appendChild(requiredMark);
1733
+ }
1734
+ fieldWrapper.appendChild(label);
1735
+ }
1736
+ else {
1737
+ // Label
1738
+ const label = document.createElement('label');
1739
+ label.style.cssText = `display: block; font-size: 14px; font-weight: 500; margin-bottom: 4px; color: ${this.escapeHTML(textColor)};`;
1740
+ label.textContent = field.label + ' ';
1741
+ if (field.required) {
1742
+ const requiredMark = document.createElement('span');
1743
+ requiredMark.style.color = '#EF4444';
1744
+ requiredMark.textContent = '*';
1745
+ label.appendChild(requiredMark);
1746
+ }
1747
+ fieldWrapper.appendChild(label);
1748
+ // Input/Textarea
1749
+ if (field.type === 'textarea') {
1750
+ const textarea = document.createElement('textarea');
1751
+ textarea.name = field.name;
1752
+ if (field.placeholder)
1753
+ textarea.placeholder = field.placeholder;
1754
+ if (field.required)
1755
+ textarea.required = true;
1756
+ textarea.style.cssText = 'width: 100%; padding: 8px 12px; border: 1px solid #E4E4E7; border-radius: 6px; font-size: 14px; resize: vertical; min-height: 80px; box-sizing: border-box;';
1757
+ fieldWrapper.appendChild(textarea);
1758
+ }
1759
+ else {
1760
+ const input = document.createElement('input');
1761
+ input.type = field.type;
1762
+ input.name = field.name;
1763
+ if (field.placeholder)
1764
+ input.placeholder = field.placeholder;
1765
+ if (field.required)
1766
+ input.required = true;
1767
+ input.style.cssText = 'width: 100%; padding: 8px 12px; border: 1px solid #E4E4E7; border-radius: 6px; font-size: 14px; box-sizing: border-box;';
1768
+ fieldWrapper.appendChild(input);
1769
+ }
1770
+ }
1771
+ formElement.appendChild(fieldWrapper);
1772
+ });
1773
+ // Submit button
1774
+ const submitBtn = document.createElement('button');
1775
+ submitBtn.type = 'submit';
1776
+ submitBtn.style.cssText = `
1777
+ width: 100%;
1778
+ padding: 10px 16px;
1779
+ background: ${this.escapeHTML(primaryColor)};
1780
+ color: white;
1781
+ border: none;
1782
+ border-radius: 6px;
1783
+ font-size: 14px;
1784
+ font-weight: 500;
1785
+ cursor: pointer;
1786
+ margin-top: 8px;
1787
+ `;
1788
+ submitBtn.textContent = form.submitButtonText || 'Subscribe';
1789
+ formElement.appendChild(submitBtn);
1790
+ container.appendChild(formElement);
1791
+ }
1593
1792
  buildFormHTML(form) {
1594
1793
  const style = form.style || {};
1595
1794
  const primaryColor = style.primaryColor || '#10B981';
@@ -1739,19 +1938,29 @@ class PopupFormsPlugin extends BasePlugin {
1739
1938
  });
1740
1939
  const result = await response.json();
1741
1940
  if (result.success) {
1742
- // Show success message
1743
- container.innerHTML = `
1744
- <div style="text-align: center; padding: 20px;">
1745
- <div style="width: 48px; height: 48px; background: #10B981; border-radius: 50%; margin: 0 auto 16px; display: flex; align-items: center; justify-content: center;">
1746
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
1747
- <polyline points="20 6 9 17 4 12"></polyline>
1748
- </svg>
1749
- </div>
1750
- <p style="font-size: 16px; font-weight: 500; color: #18181B;">
1751
- ${form.successMessage || 'Thank you!'}
1752
- </p>
1753
- </div>
1754
- `;
1941
+ // Show success message using safe DOM APIs
1942
+ container.innerHTML = '';
1943
+ const successWrapper = document.createElement('div');
1944
+ successWrapper.style.cssText = 'text-align: center; padding: 20px;';
1945
+ const iconWrapper = document.createElement('div');
1946
+ iconWrapper.style.cssText = 'width: 48px; height: 48px; background: #10B981; border-radius: 50%; margin: 0 auto 16px; display: flex; align-items: center; justify-content: center;';
1947
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
1948
+ svg.setAttribute('width', '24');
1949
+ svg.setAttribute('height', '24');
1950
+ svg.setAttribute('viewBox', '0 0 24 24');
1951
+ svg.setAttribute('fill', 'none');
1952
+ svg.setAttribute('stroke', 'white');
1953
+ svg.setAttribute('stroke-width', '2');
1954
+ const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
1955
+ polyline.setAttribute('points', '20 6 9 17 4 12');
1956
+ svg.appendChild(polyline);
1957
+ iconWrapper.appendChild(svg);
1958
+ const message = document.createElement('p');
1959
+ message.style.cssText = 'font-size: 16px; font-weight: 500; color: #18181B;';
1960
+ message.textContent = form.successMessage || 'Thank you!';
1961
+ successWrapper.appendChild(iconWrapper);
1962
+ successWrapper.appendChild(message);
1963
+ container.appendChild(successWrapper);
1755
1964
  // Track identify
1756
1965
  if (data.email) {
1757
1966
  this.tracker?.identify(data.email, data);
@@ -1888,6 +2097,8 @@ function hasStoredConsent() {
1888
2097
  * Manages consent state and event buffering for GDPR/CCPA compliance
1889
2098
  * @see SDK_VERSION in core/config.ts
1890
2099
  */
2100
+ /** Maximum events to buffer while waiting for consent */
2101
+ const MAX_BUFFER_SIZE = 100;
1891
2102
  /**
1892
2103
  * Manages user consent state for tracking
1893
2104
  */
@@ -2004,6 +2215,11 @@ class ConsentManager {
2004
2215
  * Buffer an event (for waitForConsent mode)
2005
2216
  */
2006
2217
  bufferEvent(event) {
2218
+ // Prevent unbounded buffer growth
2219
+ if (this.eventBuffer.length >= MAX_BUFFER_SIZE) {
2220
+ logger.warn('Consent event buffer full, dropping oldest event');
2221
+ this.eventBuffer.shift();
2222
+ }
2007
2223
  this.eventBuffer.push(event);
2008
2224
  logger.debug('Event buffered (waiting for consent):', event.eventName);
2009
2225
  }
@@ -2148,6 +2364,7 @@ class Tracker {
2148
2364
  }
2149
2365
  /**
2150
2366
  * Initialize enabled plugins
2367
+ * Handles both sync and async plugin init methods
2151
2368
  */
2152
2369
  initPlugins() {
2153
2370
  const pluginsToLoad = this.config.plugins;
@@ -2158,7 +2375,13 @@ class Tracker {
2158
2375
  for (const pluginName of filteredPlugins) {
2159
2376
  try {
2160
2377
  const plugin = getPlugin(pluginName);
2161
- plugin.init(this);
2378
+ // Handle both sync and async init (fire-and-forget for async)
2379
+ const result = plugin.init(this);
2380
+ if (result instanceof Promise) {
2381
+ result.catch((error) => {
2382
+ logger.error(`Async plugin init failed: ${pluginName}`, error);
2383
+ });
2384
+ }
2162
2385
  this.plugins.push(plugin);
2163
2386
  logger.debug(`Plugin loaded: ${pluginName}`);
2164
2387
  }
@@ -2336,10 +2559,10 @@ class Tracker {
2336
2559
  /**
2337
2560
  * Destroy tracker and cleanup
2338
2561
  */
2339
- destroy() {
2562
+ async destroy() {
2340
2563
  logger.info('Destroying tracker');
2341
- // Flush any remaining events
2342
- this.queue.flush();
2564
+ // Flush any remaining events (await to ensure completion)
2565
+ await this.queue.flush();
2343
2566
  // Destroy plugins
2344
2567
  for (const plugin of this.plugins) {
2345
2568
  if (plugin.destroy) {
@@ -2524,6 +2747,304 @@ class CRMClient {
2524
2747
  body: JSON.stringify({ stageId }),
2525
2748
  });
2526
2749
  }
2750
+ // ============================================
2751
+ // COMPANIES API
2752
+ // ============================================
2753
+ /**
2754
+ * Get all companies with pagination
2755
+ */
2756
+ async getCompanies(params) {
2757
+ const queryParams = new URLSearchParams();
2758
+ if (params?.page)
2759
+ queryParams.set('page', params.page.toString());
2760
+ if (params?.limit)
2761
+ queryParams.set('limit', params.limit.toString());
2762
+ if (params?.search)
2763
+ queryParams.set('search', params.search);
2764
+ if (params?.status)
2765
+ queryParams.set('status', params.status);
2766
+ if (params?.industry)
2767
+ queryParams.set('industry', params.industry);
2768
+ const query = queryParams.toString();
2769
+ const endpoint = `/api/workspaces/${this.workspaceId}/companies${query ? `?${query}` : ''}`;
2770
+ return this.request(endpoint);
2771
+ }
2772
+ /**
2773
+ * Get a single company by ID
2774
+ */
2775
+ async getCompany(companyId) {
2776
+ return this.request(`/api/workspaces/${this.workspaceId}/companies/${companyId}`);
2777
+ }
2778
+ /**
2779
+ * Create a new company
2780
+ */
2781
+ async createCompany(company) {
2782
+ return this.request(`/api/workspaces/${this.workspaceId}/companies`, {
2783
+ method: 'POST',
2784
+ body: JSON.stringify(company),
2785
+ });
2786
+ }
2787
+ /**
2788
+ * Update an existing company
2789
+ */
2790
+ async updateCompany(companyId, updates) {
2791
+ return this.request(`/api/workspaces/${this.workspaceId}/companies/${companyId}`, {
2792
+ method: 'PUT',
2793
+ body: JSON.stringify(updates),
2794
+ });
2795
+ }
2796
+ /**
2797
+ * Delete a company
2798
+ */
2799
+ async deleteCompany(companyId) {
2800
+ return this.request(`/api/workspaces/${this.workspaceId}/companies/${companyId}`, {
2801
+ method: 'DELETE',
2802
+ });
2803
+ }
2804
+ /**
2805
+ * Get contacts belonging to a company
2806
+ */
2807
+ async getCompanyContacts(companyId, params) {
2808
+ const queryParams = new URLSearchParams();
2809
+ if (params?.page)
2810
+ queryParams.set('page', params.page.toString());
2811
+ if (params?.limit)
2812
+ queryParams.set('limit', params.limit.toString());
2813
+ const query = queryParams.toString();
2814
+ const endpoint = `/api/workspaces/${this.workspaceId}/companies/${companyId}/contacts${query ? `?${query}` : ''}`;
2815
+ return this.request(endpoint);
2816
+ }
2817
+ /**
2818
+ * Get deals/opportunities belonging to a company
2819
+ */
2820
+ async getCompanyDeals(companyId, params) {
2821
+ const queryParams = new URLSearchParams();
2822
+ if (params?.page)
2823
+ queryParams.set('page', params.page.toString());
2824
+ if (params?.limit)
2825
+ queryParams.set('limit', params.limit.toString());
2826
+ const query = queryParams.toString();
2827
+ const endpoint = `/api/workspaces/${this.workspaceId}/companies/${companyId}/deals${query ? `?${query}` : ''}`;
2828
+ return this.request(endpoint);
2829
+ }
2830
+ // ============================================
2831
+ // PIPELINES API
2832
+ // ============================================
2833
+ /**
2834
+ * Get all pipelines
2835
+ */
2836
+ async getPipelines() {
2837
+ return this.request(`/api/workspaces/${this.workspaceId}/pipelines`);
2838
+ }
2839
+ /**
2840
+ * Get a single pipeline by ID
2841
+ */
2842
+ async getPipeline(pipelineId) {
2843
+ return this.request(`/api/workspaces/${this.workspaceId}/pipelines/${pipelineId}`);
2844
+ }
2845
+ /**
2846
+ * Create a new pipeline
2847
+ */
2848
+ async createPipeline(pipeline) {
2849
+ return this.request(`/api/workspaces/${this.workspaceId}/pipelines`, {
2850
+ method: 'POST',
2851
+ body: JSON.stringify(pipeline),
2852
+ });
2853
+ }
2854
+ /**
2855
+ * Update an existing pipeline
2856
+ */
2857
+ async updatePipeline(pipelineId, updates) {
2858
+ return this.request(`/api/workspaces/${this.workspaceId}/pipelines/${pipelineId}`, {
2859
+ method: 'PUT',
2860
+ body: JSON.stringify(updates),
2861
+ });
2862
+ }
2863
+ /**
2864
+ * Delete a pipeline
2865
+ */
2866
+ async deletePipeline(pipelineId) {
2867
+ return this.request(`/api/workspaces/${this.workspaceId}/pipelines/${pipelineId}`, {
2868
+ method: 'DELETE',
2869
+ });
2870
+ }
2871
+ // ============================================
2872
+ // TASKS API
2873
+ // ============================================
2874
+ /**
2875
+ * Get all tasks with pagination
2876
+ */
2877
+ async getTasks(params) {
2878
+ const queryParams = new URLSearchParams();
2879
+ if (params?.page)
2880
+ queryParams.set('page', params.page.toString());
2881
+ if (params?.limit)
2882
+ queryParams.set('limit', params.limit.toString());
2883
+ if (params?.status)
2884
+ queryParams.set('status', params.status);
2885
+ if (params?.priority)
2886
+ queryParams.set('priority', params.priority);
2887
+ if (params?.contactId)
2888
+ queryParams.set('contactId', params.contactId);
2889
+ if (params?.companyId)
2890
+ queryParams.set('companyId', params.companyId);
2891
+ if (params?.opportunityId)
2892
+ queryParams.set('opportunityId', params.opportunityId);
2893
+ const query = queryParams.toString();
2894
+ const endpoint = `/api/workspaces/${this.workspaceId}/tasks${query ? `?${query}` : ''}`;
2895
+ return this.request(endpoint);
2896
+ }
2897
+ /**
2898
+ * Get a single task by ID
2899
+ */
2900
+ async getTask(taskId) {
2901
+ return this.request(`/api/workspaces/${this.workspaceId}/tasks/${taskId}`);
2902
+ }
2903
+ /**
2904
+ * Create a new task
2905
+ */
2906
+ async createTask(task) {
2907
+ return this.request(`/api/workspaces/${this.workspaceId}/tasks`, {
2908
+ method: 'POST',
2909
+ body: JSON.stringify(task),
2910
+ });
2911
+ }
2912
+ /**
2913
+ * Update an existing task
2914
+ */
2915
+ async updateTask(taskId, updates) {
2916
+ return this.request(`/api/workspaces/${this.workspaceId}/tasks/${taskId}`, {
2917
+ method: 'PUT',
2918
+ body: JSON.stringify(updates),
2919
+ });
2920
+ }
2921
+ /**
2922
+ * Mark a task as completed
2923
+ */
2924
+ async completeTask(taskId) {
2925
+ return this.request(`/api/workspaces/${this.workspaceId}/tasks/${taskId}/complete`, {
2926
+ method: 'PATCH',
2927
+ });
2928
+ }
2929
+ /**
2930
+ * Delete a task
2931
+ */
2932
+ async deleteTask(taskId) {
2933
+ return this.request(`/api/workspaces/${this.workspaceId}/tasks/${taskId}`, {
2934
+ method: 'DELETE',
2935
+ });
2936
+ }
2937
+ // ============================================
2938
+ // ACTIVITIES API
2939
+ // ============================================
2940
+ /**
2941
+ * Get activities for a contact
2942
+ */
2943
+ async getContactActivities(contactId, params) {
2944
+ const queryParams = new URLSearchParams();
2945
+ if (params?.page)
2946
+ queryParams.set('page', params.page.toString());
2947
+ if (params?.limit)
2948
+ queryParams.set('limit', params.limit.toString());
2949
+ if (params?.type)
2950
+ queryParams.set('type', params.type);
2951
+ const query = queryParams.toString();
2952
+ const endpoint = `/api/workspaces/${this.workspaceId}/contacts/${contactId}/activities${query ? `?${query}` : ''}`;
2953
+ return this.request(endpoint);
2954
+ }
2955
+ /**
2956
+ * Get activities for an opportunity/deal
2957
+ */
2958
+ async getOpportunityActivities(opportunityId, params) {
2959
+ const queryParams = new URLSearchParams();
2960
+ if (params?.page)
2961
+ queryParams.set('page', params.page.toString());
2962
+ if (params?.limit)
2963
+ queryParams.set('limit', params.limit.toString());
2964
+ if (params?.type)
2965
+ queryParams.set('type', params.type);
2966
+ const query = queryParams.toString();
2967
+ const endpoint = `/api/workspaces/${this.workspaceId}/opportunities/${opportunityId}/activities${query ? `?${query}` : ''}`;
2968
+ return this.request(endpoint);
2969
+ }
2970
+ /**
2971
+ * Create a new activity
2972
+ */
2973
+ async createActivity(activity) {
2974
+ // Determine the correct endpoint based on related entity
2975
+ let endpoint;
2976
+ if (activity.opportunityId) {
2977
+ endpoint = `/api/workspaces/${this.workspaceId}/opportunities/${activity.opportunityId}/activities`;
2978
+ }
2979
+ else if (activity.contactId) {
2980
+ endpoint = `/api/workspaces/${this.workspaceId}/contacts/${activity.contactId}/activities`;
2981
+ }
2982
+ else {
2983
+ endpoint = `/api/workspaces/${this.workspaceId}/activities`;
2984
+ }
2985
+ return this.request(endpoint, {
2986
+ method: 'POST',
2987
+ body: JSON.stringify(activity),
2988
+ });
2989
+ }
2990
+ /**
2991
+ * Update an existing activity
2992
+ */
2993
+ async updateActivity(activityId, updates) {
2994
+ return this.request(`/api/workspaces/${this.workspaceId}/activities/${activityId}`, {
2995
+ method: 'PATCH',
2996
+ body: JSON.stringify(updates),
2997
+ });
2998
+ }
2999
+ /**
3000
+ * Delete an activity
3001
+ */
3002
+ async deleteActivity(activityId) {
3003
+ return this.request(`/api/workspaces/${this.workspaceId}/activities/${activityId}`, {
3004
+ method: 'DELETE',
3005
+ });
3006
+ }
3007
+ /**
3008
+ * Log a call activity
3009
+ */
3010
+ async logCall(data) {
3011
+ return this.createActivity({
3012
+ type: 'call',
3013
+ title: `${data.direction === 'inbound' ? 'Inbound' : 'Outbound'} Call`,
3014
+ direction: data.direction,
3015
+ duration: data.duration,
3016
+ outcome: data.outcome,
3017
+ description: data.notes,
3018
+ contactId: data.contactId,
3019
+ opportunityId: data.opportunityId,
3020
+ });
3021
+ }
3022
+ /**
3023
+ * Log a meeting activity
3024
+ */
3025
+ async logMeeting(data) {
3026
+ return this.createActivity({
3027
+ type: 'meeting',
3028
+ title: data.title,
3029
+ duration: data.duration,
3030
+ outcome: data.outcome,
3031
+ description: data.notes,
3032
+ contactId: data.contactId,
3033
+ opportunityId: data.opportunityId,
3034
+ });
3035
+ }
3036
+ /**
3037
+ * Add a note to a contact or opportunity
3038
+ */
3039
+ async addNote(data) {
3040
+ return this.createActivity({
3041
+ type: 'note',
3042
+ title: 'Note',
3043
+ description: data.content,
3044
+ contactId: data.contactId,
3045
+ opportunityId: data.opportunityId,
3046
+ });
3047
+ }
2527
3048
  }
2528
3049
 
2529
3050
  /**
@@ -2642,7 +3163,7 @@ function useClianta() {
2642
3163
  return react.useContext(CliantaContext);
2643
3164
  }
2644
3165
  /**
2645
- * useClinataTrack - Convenience hook for tracking events
3166
+ * useCliantaTrack - Convenience hook for tracking events
2646
3167
  *
2647
3168
  * @example
2648
3169
  * const track = useCliantaTrack();