@salesmind-ai/design-system 0.1.6 → 0.1.8

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/web/index.js CHANGED
@@ -1,5 +1,6 @@
1
+ "use client";
1
2
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import React, { forwardRef, useState, useEffect, createContext, useCallback, useMemo, useContext } from 'react';
3
+ import React, { createContext, forwardRef, useState, useEffect, useContext, useCallback, useMemo } from 'react';
3
4
  import { Slot } from '@radix-ui/react-slot';
4
5
  import clsx from 'clsx';
5
6
  import { AnimatePresence, motion } from 'framer-motion';
@@ -649,23 +650,146 @@ var Button = React.forwardRef(
649
650
  }
650
651
  );
651
652
  Button.displayName = "Button";
653
+ var UtmContext = createContext(null);
654
+
655
+ // src/web/utm/useUtmDefaults.ts
656
+ function useUtmDefaults() {
657
+ return useContext(UtmContext);
658
+ }
659
+
660
+ // src/web/utm/builders.ts
661
+ var PLACEHOLDER_ORIGIN = "https://__placeholder__.internal";
662
+ function buildUtmUrl(baseUrl, params) {
663
+ const isRelative = baseUrl.startsWith("/");
664
+ let url;
665
+ try {
666
+ url = isRelative ? new URL(baseUrl, PLACEHOLDER_ORIGIN) : new URL(baseUrl);
667
+ } catch {
668
+ return baseUrl;
669
+ }
670
+ const existingParams = [];
671
+ url.searchParams.forEach((value, key) => {
672
+ existingParams.push([key, value]);
673
+ });
674
+ for (const [key] of existingParams) {
675
+ url.searchParams.delete(key);
676
+ }
677
+ for (const [key, value] of existingParams) {
678
+ if (!key.startsWith("utm_")) {
679
+ url.searchParams.set(key, value);
680
+ }
681
+ }
682
+ url.searchParams.set("utm_source", params.source);
683
+ url.searchParams.set("utm_medium", params.medium);
684
+ url.searchParams.set("utm_campaign", params.campaign);
685
+ if (params.term !== void 0) {
686
+ url.searchParams.set("utm_term", params.term);
687
+ }
688
+ if (params.content !== void 0) {
689
+ url.searchParams.set("utm_content", params.content);
690
+ }
691
+ if (isRelative) {
692
+ return url.href.replace(PLACEHOLDER_ORIGIN, "");
693
+ }
694
+ return url.href;
695
+ }
696
+
697
+ // src/web/utm/classifiers.ts
698
+ var INTERNAL_PATTERNS = [
699
+ /^\/(?!\/)/,
700
+ // Relative paths
701
+ /^https?:\/\/(www\.)?sales-mind\.ai/i,
702
+ // Marketing site
703
+ /^https?:\/\/app\.sales-mind\.ai/i,
704
+ // Web app
705
+ /^https?:\/\/apps\.sales-mind\.ai/i,
706
+ // Web app (legacy)
707
+ /^https?:\/\/meet\.sales-mind\.ai/i,
708
+ // Booking
709
+ /^https?:\/\/docs\.sales-mind\.ai/i
710
+ // Docs
711
+ ];
712
+ var SYSTEM_PATTERNS = [
713
+ /^https?:\/\/.*\/api\//i,
714
+ // API endpoints
715
+ /^https?:\/\/.*\/webhook/i,
716
+ // Webhooks
717
+ /^https?:\/\/.*\/oauth/i,
718
+ // OAuth callbacks
719
+ /^https?:\/\/.*\/callback/i,
720
+ // Callbacks
721
+ /^https?:\/\/.*\.supabase\.(co|com)/i,
722
+ // Supabase
723
+ /^https?:\/\/.*\.firebaseapp\.com/i,
724
+ // Firebase
725
+ /^https?:\/\/.*\.cloudfunctions\.net/i
726
+ // Cloud Functions
727
+ ];
728
+ var ASSET_PATTERNS = [
729
+ /\.(css|js|mjs|map|woff2?|ttf|eot|svg|png|jpe?g|gif|webp|avif|ico|pdf)(\?.*)?$/i
730
+ ];
731
+ var CONVERSION_PATTERNS = [
732
+ /^https?:\/\/(www\.)?calendly\.com/i,
733
+ /^https?:\/\/(checkout\.)?stripe\.com/i,
734
+ /^https?:\/\/buy\.stripe\.com/i,
735
+ /^https?:\/\/chromewebstore\.google\.com/i,
736
+ /^https?:\/\/meet\.sales-mind\.ai/i
737
+ ];
738
+ var PROTOCOL_EXEMPT = [
739
+ /^mailto:/i,
740
+ /^tel:/i,
741
+ /^#/,
742
+ /^javascript:/i
743
+ ];
744
+ function classifyUrl(url) {
745
+ if (PROTOCOL_EXEMPT.some((p) => p.test(url))) return "protocol";
746
+ if (ASSET_PATTERNS.some((p) => p.test(url))) return "asset";
747
+ if (SYSTEM_PATTERNS.some((p) => p.test(url))) return "system";
748
+ if (CONVERSION_PATTERNS.some((p) => p.test(url))) return "conversion";
749
+ if (INTERNAL_PATTERNS.some((p) => p.test(url))) return "internal";
750
+ return "external";
751
+ }
752
+ function requiresUtm(url) {
753
+ const classification = classifyUrl(url);
754
+ return classification === "external" || classification === "conversion";
755
+ }
756
+ function isUtmExempt(url) {
757
+ const classification = classifyUrl(url);
758
+ return classification === "protocol" || classification === "asset" || classification === "system" || classification === "internal";
759
+ }
652
760
 
653
761
  // src/components/OutboundLink/outbound-link-utils.ts
654
- var CONSTANT_UTM_SOURCE = "salesmind";
762
+ var LEGACY_UTM_SOURCE = "salesmind";
655
763
  var EXEMPT_PATTERNS = [
656
764
  /^https?:\/\/(www\.)?(stripe\.com|checkout\.stripe\.com|paypal\.com)/i,
657
765
  /^https?:\/\/(www\.)?github\.com\/login\/oauth/i
658
766
  ];
659
767
  var isExemptUrl = (urlStr) => {
660
768
  if (urlStr.startsWith("mailto:") || urlStr.startsWith("tel:")) return true;
769
+ const classification = classifyUrl(urlStr);
770
+ if (classification === "system" || classification === "protocol" || classification === "asset") {
771
+ return true;
772
+ }
661
773
  return EXEMPT_PATTERNS.some((pattern) => pattern.test(urlStr));
662
774
  };
775
+ var appendGovernedUTMs = (href, params, preserveExisting = true) => {
776
+ try {
777
+ const url = new URL(href);
778
+ if (preserveExisting) {
779
+ const hasAll = url.searchParams.has("utm_source") && url.searchParams.has("utm_medium") && url.searchParams.has("utm_campaign");
780
+ if (hasAll) return href;
781
+ }
782
+ return buildUtmUrl(href, params);
783
+ } catch {
784
+ return href;
785
+ }
786
+ };
663
787
  var appendUTMs = (href, context, pageSlug, options) => {
664
788
  try {
665
789
  const url = new URL(href);
666
790
  const { mediumOverride = "outbound_link", campaignOverride, preserveExisting = true } = options;
667
791
  const utms = {
668
- utm_source: CONSTANT_UTM_SOURCE,
792
+ utm_source: LEGACY_UTM_SOURCE,
669
793
  utm_medium: mediumOverride,
670
794
  utm_campaign: campaignOverride || pageSlug,
671
795
  utm_content: context
@@ -692,10 +816,13 @@ var OutboundLink = forwardRef(
692
816
  preserveExistingUTM = true,
693
817
  openInNewTab = true,
694
818
  disableTracking = false,
819
+ utmParams,
695
820
  onClick,
696
821
  children,
697
822
  ...props
698
823
  }, ref) => {
824
+ const contextParams = useUtmDefaults();
825
+ const resolvedUtmParams = utmParams ?? contextParams;
699
826
  let hostname = "";
700
827
  try {
701
828
  const url = new URL(href);
@@ -720,16 +847,20 @@ var OutboundLink = forwardRef(
720
847
  }
721
848
  const isExempt = isExemptUrl(href) || disableTracking;
722
849
  if (isExternal && !isExempt) {
723
- const pageSlug = window.location.pathname.replace(/^\/|\/$/g, "") || "home";
724
- setFinalHref(appendUTMs(href, context, pageSlug, {
725
- mediumOverride: currentMedium,
726
- campaignOverride,
727
- preserveExisting: preserveExistingUTM
728
- }));
850
+ if (resolvedUtmParams) {
851
+ setFinalHref(appendGovernedUTMs(href, resolvedUtmParams, preserveExistingUTM));
852
+ } else {
853
+ const pageSlug = window.location.pathname.replace(/^\/|\/$/g, "") || "home";
854
+ setFinalHref(appendUTMs(href, context, pageSlug, {
855
+ mediumOverride: currentMedium,
856
+ campaignOverride,
857
+ preserveExisting: preserveExistingUTM
858
+ }));
859
+ }
729
860
  } else {
730
861
  setFinalHref(href);
731
862
  }
732
- }, [href, context, mediumOverride, campaignOverride, preserveExistingUTM, disableTracking]);
863
+ }, [href, context, mediumOverride, campaignOverride, preserveExistingUTM, disableTracking, resolvedUtmParams]);
733
864
  const handleClick = (e) => {
734
865
  if (typeof window === "undefined" || disableTracking) {
735
866
  onClick?.(e);
@@ -902,6 +1033,268 @@ function useAnalytics() {
902
1033
  return useContext(AnalyticsContext) ?? NOOP_VALUE;
903
1034
  }
904
1035
 
905
- export { AnalyticsProvider, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, CookieConsent, JsonLd, aggregateRatingFromTestimonials, buildPageGraph, canonicalUrl, createAnalyticsLoader, createEntityIds, createSchemaGenerators, loadClarity, loadGoogleAnalytics, useAnalytics, useCookieConsent };
1036
+ // src/web/utm/constants.ts
1037
+ var UTM_SOURCES = [
1038
+ "linkedin",
1039
+ "whatsapp",
1040
+ "intercom",
1041
+ "email",
1042
+ "website",
1043
+ "app",
1044
+ "chromeStore",
1045
+ "stripe",
1046
+ "direct"
1047
+ ];
1048
+ var UTM_MEDIUMS_MESSAGING = [
1049
+ "dm",
1050
+ "group",
1051
+ "email",
1052
+ "inAppChat",
1053
+ "organicPost",
1054
+ "paidAd",
1055
+ "qrCode",
1056
+ "redirect",
1057
+ "storeListing"
1058
+ ];
1059
+ var UTM_MEDIUMS_APP = [
1060
+ "appHome",
1061
+ "appDashboard",
1062
+ "appInbox",
1063
+ "appContacts",
1064
+ "appCampaigns",
1065
+ "appSettings",
1066
+ "appBilling",
1067
+ "appCheckout",
1068
+ "appOnboarding",
1069
+ "appSupport",
1070
+ "appCalendar"
1071
+ ];
1072
+ var UTM_MEDIUMS_WEB = [
1073
+ "webHome",
1074
+ "webDemo",
1075
+ "webPricing",
1076
+ "webFeatures",
1077
+ "webUseCase",
1078
+ "webSolution",
1079
+ "webIndustry",
1080
+ "webIntegrations",
1081
+ "webBlog",
1082
+ "webBlogPost",
1083
+ "webLanding",
1084
+ "webComparison",
1085
+ "webCaseStudy",
1086
+ "webAbout",
1087
+ "webContact",
1088
+ "webCareers",
1089
+ "webLegal",
1090
+ "webDocs",
1091
+ "webAffiliate"
1092
+ ];
1093
+ var UTM_MEDIUMS_ALL = [
1094
+ ...UTM_MEDIUMS_MESSAGING,
1095
+ ...UTM_MEDIUMS_APP,
1096
+ ...UTM_MEDIUMS_WEB
1097
+ ];
1098
+ var UTM_CAMPAIGNS = [
1099
+ "discoveryCall",
1100
+ "demo",
1101
+ "trial",
1102
+ "onboarding",
1103
+ "upgrade",
1104
+ "renewal",
1105
+ "retention",
1106
+ "accountSupport",
1107
+ "supportCall",
1108
+ "partnerCall",
1109
+ "hiring",
1110
+ "interviewCall"
1111
+ ];
1112
+ var UTM_TERMS = [
1113
+ "julienGadea",
1114
+ "bramSmith",
1115
+ "florentDupont",
1116
+ "sawLin",
1117
+ "evaSupport",
1118
+ "team",
1119
+ "auto"
1120
+ ];
1121
+ var UTM_CONTENTS = [
1122
+ "ctaPrimary",
1123
+ "ctaSecondary",
1124
+ "ctaHeader",
1125
+ "ctaFooter",
1126
+ "ctaInline",
1127
+ "buttonPrimary",
1128
+ "buttonSecondary",
1129
+ "banner",
1130
+ "popup",
1131
+ "variantA",
1132
+ "variantB",
1133
+ "variantC"
1134
+ ];
1135
+ var UTM_SOURCES_REQUIRING_SELLER = [
1136
+ "linkedin",
1137
+ "whatsapp",
1138
+ "intercom",
1139
+ "email"
1140
+ ];
1141
+
1142
+ // src/web/utm/validators.ts
1143
+ function validateUtmField(field, value) {
1144
+ switch (field) {
1145
+ case "source":
1146
+ return UTM_SOURCES.includes(value);
1147
+ case "medium":
1148
+ return UTM_MEDIUMS_ALL.includes(value);
1149
+ case "campaign":
1150
+ return UTM_CAMPAIGNS.includes(value);
1151
+ case "term":
1152
+ return UTM_TERMS.includes(value);
1153
+ case "content":
1154
+ return UTM_CONTENTS.includes(value);
1155
+ default:
1156
+ return false;
1157
+ }
1158
+ }
1159
+ function isValidUtmParams(params) {
1160
+ if (!params.source || !params.medium || !params.campaign) {
1161
+ return false;
1162
+ }
1163
+ if (!validateUtmField("source", params.source)) return false;
1164
+ if (!validateUtmField("medium", params.medium)) return false;
1165
+ if (!validateUtmField("campaign", params.campaign)) return false;
1166
+ if (params.term !== void 0 && !validateUtmField("term", params.term)) return false;
1167
+ if (params.content !== void 0 && !validateUtmField("content", params.content)) return false;
1168
+ return true;
1169
+ }
1170
+ function validateCompliance(url, params) {
1171
+ const errors = [];
1172
+ const classification = classifyUrl(url);
1173
+ const needsUtm = requiresUtm(url);
1174
+ if (!needsUtm) {
1175
+ return {
1176
+ status: "compliant",
1177
+ url,
1178
+ params: params ?? null,
1179
+ errors: []
1180
+ };
1181
+ }
1182
+ if (!params) {
1183
+ return {
1184
+ status: "blocked",
1185
+ url,
1186
+ params: null,
1187
+ errors: [`URL classified as '${classification}' requires UTM parameters but none provided`]
1188
+ };
1189
+ }
1190
+ if (!params.source) errors.push("Missing required field: utm_source");
1191
+ if (!params.medium) errors.push("Missing required field: utm_medium");
1192
+ if (!params.campaign) errors.push("Missing required field: utm_campaign");
1193
+ if (params.source && !validateUtmField("source", params.source)) {
1194
+ errors.push(`Invalid utm_source: '${params.source}'. Must be one of: ${UTM_SOURCES.join(", ")}`);
1195
+ }
1196
+ if (params.medium && !validateUtmField("medium", params.medium)) {
1197
+ errors.push(`Invalid utm_medium: '${params.medium}'. Must be one of governed enum values`);
1198
+ }
1199
+ if (params.campaign && !validateUtmField("campaign", params.campaign)) {
1200
+ errors.push(`Invalid utm_campaign: '${params.campaign}'. Must be one of: ${UTM_CAMPAIGNS.join(", ")}`);
1201
+ }
1202
+ if (params.term !== void 0 && params.term && !validateUtmField("term", params.term)) {
1203
+ errors.push(`Invalid utm_term: '${params.term}'. Must be one of: ${UTM_TERMS.join(", ")}`);
1204
+ }
1205
+ if (params.content !== void 0 && params.content && !validateUtmField("content", params.content)) {
1206
+ errors.push(`Invalid utm_content: '${params.content}'. Must be one of: ${UTM_CONTENTS.join(", ")}`);
1207
+ }
1208
+ const allValues = [];
1209
+ if (params.source) allValues.push(params.source);
1210
+ if (params.medium) allValues.push(params.medium);
1211
+ if (params.campaign) allValues.push(params.campaign);
1212
+ if (params.term) allValues.push(params.term);
1213
+ if (params.content) allValues.push(params.content);
1214
+ for (const value of allValues) {
1215
+ if (/\s/.test(value)) errors.push(`Value '${value}' contains spaces`);
1216
+ if (/_/.test(value)) errors.push(`Value '${value}' contains underscores`);
1217
+ if (/-/.test(value)) errors.push(`Value '${value}' contains hyphens`);
1218
+ if (/[^\x20-\x7E]/.test(value)) errors.push(`Value '${value}' contains non-ASCII characters`);
1219
+ if (value === value.toUpperCase() && value.length > 1) errors.push(`Value '${value}' is ALL CAPS`);
1220
+ }
1221
+ const status = errors.length === 0 ? "compliant" : "blocked";
1222
+ return { status, url, params, errors };
1223
+ }
1224
+ function classifyAndEnforce(url, params) {
1225
+ return validateCompliance(url, params);
1226
+ }
1227
+ function buildBlockedError(reason, url) {
1228
+ return {
1229
+ status: "UTM_COMPLIANCE_BLOCKED",
1230
+ reason,
1231
+ requiredFix: reason,
1232
+ correctedExample: url ?? null
1233
+ };
1234
+ }
1235
+
1236
+ // src/web/utm/attribution.ts
1237
+ function parseUtmParams(search) {
1238
+ if (!search) return {};
1239
+ const params = new URLSearchParams(search);
1240
+ const result = {};
1241
+ const source = params.get("utm_source");
1242
+ if (source && UTM_SOURCES.includes(source)) {
1243
+ result.source = source;
1244
+ }
1245
+ const medium = params.get("utm_medium");
1246
+ if (medium) {
1247
+ result.medium = medium;
1248
+ }
1249
+ const campaign = params.get("utm_campaign");
1250
+ if (campaign && UTM_CAMPAIGNS.includes(campaign)) {
1251
+ result.campaign = campaign;
1252
+ }
1253
+ const term = params.get("utm_term");
1254
+ if (term && UTM_TERMS.includes(term)) {
1255
+ result.term = term;
1256
+ }
1257
+ const content = params.get("utm_content");
1258
+ if (content && UTM_CONTENTS.includes(content)) {
1259
+ result.content = content;
1260
+ }
1261
+ return result;
1262
+ }
1263
+ function toFirstTouchAttribution(params) {
1264
+ return {
1265
+ firstTouchSource: params.source,
1266
+ firstTouchMedium: params.medium,
1267
+ firstTouchCampaign: params.campaign,
1268
+ firstTouchSeller: params.term ?? null
1269
+ };
1270
+ }
1271
+ function requiresSellerAttribution(source) {
1272
+ return UTM_SOURCES_REQUIRING_SELLER.includes(source);
1273
+ }
1274
+
1275
+ // src/web/utm/audit.ts
1276
+ function createAuditEntry(url, params, generatorContext, confidence) {
1277
+ const utmParts = [];
1278
+ if (params) {
1279
+ if (params.source) utmParts.push(`utm_source=${params.source}`);
1280
+ if (params.medium) utmParts.push(`utm_medium=${params.medium}`);
1281
+ if (params.campaign) utmParts.push(`utm_campaign=${params.campaign}`);
1282
+ if (params.term) utmParts.push(`utm_term=${params.term}`);
1283
+ if (params.content) utmParts.push(`utm_content=${params.content}`);
1284
+ }
1285
+ return {
1286
+ url,
1287
+ utmString: utmParts.join("&"),
1288
+ generatorContext,
1289
+ sellerAttribution: params?.term ?? null,
1290
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1291
+ confidence
1292
+ };
1293
+ }
1294
+ function UtmProvider({ params, children }) {
1295
+ return /* @__PURE__ */ jsx(UtmContext.Provider, { value: params, children });
1296
+ }
1297
+
1298
+ export { AnalyticsProvider, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, CookieConsent, JsonLd, UTM_CAMPAIGNS, UTM_CONTENTS, UTM_MEDIUMS_ALL, UTM_MEDIUMS_APP, UTM_MEDIUMS_MESSAGING, UTM_MEDIUMS_WEB, UTM_SOURCES, UTM_SOURCES_REQUIRING_SELLER, UTM_TERMS, UtmProvider, aggregateRatingFromTestimonials, buildBlockedError, buildPageGraph, buildUtmUrl, canonicalUrl, classifyAndEnforce, classifyUrl, createAnalyticsLoader, createAuditEntry, createEntityIds, createSchemaGenerators, isUtmExempt, isValidUtmParams, loadClarity, loadGoogleAnalytics, parseUtmParams, requiresSellerAttribution, requiresUtm, toFirstTouchAttribution, useAnalytics, useCookieConsent, useUtmDefaults, validateCompliance, validateUtmField };
906
1299
  //# sourceMappingURL=index.js.map
907
1300
  //# sourceMappingURL=index.js.map