@redacto.io/dpdpa-report-sdk-js 0.2.0 → 0.3.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.
@@ -35,7 +35,7 @@ var PATHS = {
35
35
  status: "/webhook/dpdpa/status"
36
36
  };
37
37
  var DEFAULT_POLL_INTERVAL_MS = 3e3;
38
- var DEFAULT_POLL_TIMEOUT_MS = 3e5;
38
+ var DEFAULT_POLL_TIMEOUT_MS = 9e4;
39
39
  var MAX_CONSECUTIVE_ERRORS = 3;
40
40
  var REQUEST_TIMEOUT_MS = 15e3;
41
41
  var STORAGE_KEY = "redacto_dpdpa_lead";
@@ -737,6 +737,291 @@ function buildCss(brand) {
737
737
  from { transform: rotate(0deg); }
738
738
  to { transform: rotate(360deg); }
739
739
  }
740
+
741
+ /* Quick insights view */
742
+
743
+ .dpdpa-card-wide {
744
+ min-height: 0;
745
+ }
746
+
747
+ .dpdpa-qi {
748
+ display: flex;
749
+ flex-direction: column;
750
+ gap: 18px;
751
+ flex: 1;
752
+ animation: dpdpa-fade-in 0.35s ease-out both;
753
+ }
754
+
755
+ .dpdpa-qi-header {
756
+ display: flex;
757
+ flex-direction: column;
758
+ gap: 12px;
759
+ }
760
+
761
+ .dpdpa-qi-title {
762
+ margin: 0;
763
+ font-size: 18px;
764
+ font-weight: 700;
765
+ color: ${brand.text};
766
+ line-height: 1.3;
767
+ }
768
+
769
+ .dpdpa-qi-subtitle {
770
+ margin: 0;
771
+ font-size: 14px;
772
+ color: ${brand.text};
773
+ opacity: 0.85;
774
+ line-height: 1.55;
775
+ }
776
+
777
+ .dpdpa-applicability {
778
+ display: flex;
779
+ flex-direction: column;
780
+ gap: 4px;
781
+ padding: 12px 14px;
782
+ border-radius: ${brand.borderRadius};
783
+ border: 1px solid ${brand.border};
784
+ background: #ffffff;
785
+ }
786
+
787
+ .dpdpa-applicability-level {
788
+ font-size: 11px;
789
+ font-weight: 700;
790
+ letter-spacing: 0.6px;
791
+ text-transform: uppercase;
792
+ }
793
+
794
+ .dpdpa-applicability-reason {
795
+ font-size: 13px;
796
+ color: ${brand.text};
797
+ opacity: 0.85;
798
+ line-height: 1.45;
799
+ }
800
+
801
+ .dpdpa-badge-strong { border-color: ${brand.primary}66; background: ${brand.primary}10; }
802
+ .dpdpa-badge-strong .dpdpa-applicability-level { color: ${brand.primary}; }
803
+
804
+ .dpdpa-badge-warn { border-color: #f59e0b66; background: #f59e0b14; }
805
+ .dpdpa-badge-warn .dpdpa-applicability-level { color: #b45309; }
806
+
807
+ .dpdpa-badge-soft { border-color: #94a3b866; background: #94a3b814; }
808
+ .dpdpa-badge-soft .dpdpa-applicability-level { color: #475569; }
809
+
810
+ .dpdpa-badge-mute { border-color: ${brand.border}; background: ${brand.background}; }
811
+ .dpdpa-badge-mute .dpdpa-applicability-level { color: ${brand.text}; opacity: 0.7; }
812
+
813
+ .dpdpa-badge-neutral { border-color: ${brand.border}; background: ${brand.background}; }
814
+ .dpdpa-badge-neutral .dpdpa-applicability-level { color: ${brand.text}; }
815
+
816
+ .dpdpa-confidence-row {
817
+ display: flex;
818
+ align-items: center;
819
+ gap: 8px;
820
+ }
821
+
822
+ .dpdpa-meta-label {
823
+ font-size: 11px;
824
+ font-weight: 700;
825
+ letter-spacing: 0.5px;
826
+ text-transform: uppercase;
827
+ color: ${brand.text};
828
+ opacity: 0.6;
829
+ }
830
+
831
+ .dpdpa-meta-value {
832
+ font-size: 13px;
833
+ color: ${brand.text};
834
+ }
835
+
836
+ .dpdpa-chip {
837
+ display: inline-flex;
838
+ align-items: center;
839
+ padding: 3px 8px;
840
+ font-size: 11px;
841
+ font-weight: 600;
842
+ color: ${brand.text};
843
+ background: ${brand.background};
844
+ border: 1px solid ${brand.border};
845
+ border-radius: 999px;
846
+ white-space: nowrap;
847
+ }
848
+
849
+ .dpdpa-chip-ref {
850
+ background: ${brand.primary}12;
851
+ border-color: ${brand.primary}33;
852
+ color: ${brand.primary};
853
+ }
854
+
855
+ .dpdpa-chip-data {
856
+ background: #ecfeff;
857
+ border-color: #67e8f933;
858
+ color: #0e7490;
859
+ }
860
+
861
+ .dpdpa-chip-touchpoint {
862
+ background: #f5f3ff;
863
+ border-color: #c4b5fd33;
864
+ color: #5b21b6;
865
+ }
866
+
867
+ .dpdpa-chip-row {
868
+ display: flex;
869
+ flex-wrap: wrap;
870
+ gap: 6px;
871
+ }
872
+
873
+ .dpdpa-callout {
874
+ padding: 12px 14px;
875
+ background: ${brand.primary}08;
876
+ border: 1px solid ${brand.primary}22;
877
+ border-radius: ${brand.borderRadius};
878
+ display: flex;
879
+ flex-direction: column;
880
+ gap: 4px;
881
+ }
882
+
883
+ .dpdpa-callout-title {
884
+ font-size: 12px;
885
+ font-weight: 700;
886
+ letter-spacing: 0.3px;
887
+ text-transform: uppercase;
888
+ color: ${brand.primary};
889
+ }
890
+
891
+ .dpdpa-callout-message {
892
+ margin: 0;
893
+ font-size: 13px;
894
+ color: ${brand.text};
895
+ line-height: 1.5;
896
+ opacity: 0.85;
897
+ }
898
+
899
+ .dpdpa-section {
900
+ display: flex;
901
+ flex-direction: column;
902
+ gap: 10px;
903
+ }
904
+
905
+ .dpdpa-section-title {
906
+ margin: 0;
907
+ font-size: 13px;
908
+ font-weight: 700;
909
+ color: ${brand.text};
910
+ letter-spacing: 0.2px;
911
+ }
912
+
913
+ .dpdpa-area-list {
914
+ display: flex;
915
+ flex-direction: column;
916
+ gap: 10px;
917
+ }
918
+
919
+ .dpdpa-area-card {
920
+ padding: 12px 14px;
921
+ background: #ffffff;
922
+ border: 1px solid ${brand.border};
923
+ border-radius: ${brand.borderRadius};
924
+ display: flex;
925
+ flex-direction: column;
926
+ gap: 8px;
927
+ }
928
+
929
+ .dpdpa-area-heading {
930
+ display: flex;
931
+ align-items: center;
932
+ justify-content: space-between;
933
+ gap: 10px;
934
+ flex-wrap: wrap;
935
+ }
936
+
937
+ .dpdpa-area-name {
938
+ margin: 0;
939
+ font-size: 14px;
940
+ font-weight: 700;
941
+ color: ${brand.text};
942
+ }
943
+
944
+ .dpdpa-product-hint {
945
+ font-size: 11px;
946
+ font-weight: 600;
947
+ color: ${brand.primary};
948
+ background: ${brand.primary}12;
949
+ padding: 3px 8px;
950
+ border-radius: 999px;
951
+ white-space: nowrap;
952
+ }
953
+
954
+ .dpdpa-area-why {
955
+ margin: 0;
956
+ font-size: 13px;
957
+ color: ${brand.text};
958
+ opacity: 0.8;
959
+ line-height: 1.5;
960
+ }
961
+
962
+ .dpdpa-cu-profile {
963
+ margin: 0;
964
+ font-size: 13px;
965
+ color: ${brand.text};
966
+ opacity: 0.85;
967
+ line-height: 1.5;
968
+ }
969
+
970
+ .dpdpa-cu-meta {
971
+ display: flex;
972
+ align-items: center;
973
+ gap: 8px;
974
+ }
975
+
976
+ .dpdpa-email-banner {
977
+ display: flex;
978
+ align-items: flex-start;
979
+ gap: 12px;
980
+ padding: 14px 16px;
981
+ background: linear-gradient(135deg, ${brand.primary}10, ${brand.accent}10);
982
+ border: 1px solid ${brand.primary}33;
983
+ border-radius: ${brand.borderRadius};
984
+ }
985
+
986
+ .dpdpa-email-icon {
987
+ flex: none;
988
+ display: inline-flex;
989
+ align-items: center;
990
+ justify-content: center;
991
+ width: 32px;
992
+ height: 32px;
993
+ border-radius: 50%;
994
+ background: ${brand.primary};
995
+ color: ${brand.primaryText};
996
+ margin-top: 2px;
997
+ }
998
+
999
+ .dpdpa-email-text {
1000
+ display: flex;
1001
+ flex-direction: column;
1002
+ gap: 2px;
1003
+ min-width: 0;
1004
+ }
1005
+
1006
+ .dpdpa-email-headline {
1007
+ font-size: 14px;
1008
+ font-weight: 700;
1009
+ color: ${brand.text};
1010
+ }
1011
+
1012
+ .dpdpa-email-sub {
1013
+ margin: 0;
1014
+ font-size: 13px;
1015
+ color: ${brand.text};
1016
+ opacity: 0.8;
1017
+ line-height: 1.5;
1018
+ word-break: break-word;
1019
+ }
1020
+
1021
+ @keyframes dpdpa-fade-in {
1022
+ from { opacity: 0; transform: translateY(6px); }
1023
+ to { opacity: 1; transform: translateY(0); }
1024
+ }
740
1025
  `;
741
1026
  }
742
1027
  function injectFormStyles(root, brand) {
@@ -974,7 +1259,7 @@ function renderIdle(brand, fields, errors, generalErrorText, handlers) {
974
1259
  el(
975
1260
  "p",
976
1261
  { className: "dpdpa-subheading" },
977
- "We'll analyse your site and deliver a compliance assessment PDF."
1262
+ "We'll preview your DPDPA exposure here and email you the full report."
978
1263
  ),
979
1264
  generalError,
980
1265
  nameRow.row,
@@ -1034,42 +1319,12 @@ function animatedDots() {
1034
1319
  el("span")
1035
1320
  );
1036
1321
  }
1037
- function stepCheck(brand) {
1038
- return el(
1039
- "div",
1040
- {
1041
- className: "dpdpa-step-icon dpdpa-step-icon-done",
1042
- style: { backgroundColor: brand.successColor }
1043
- },
1044
- svg(
1045
- "svg",
1046
- { width: 12, height: 12, viewBox: "0 0 24 24", fill: "none" },
1047
- svg("path", {
1048
- d: "M5 12l5 5L20 7",
1049
- stroke: "#ffffff",
1050
- "stroke-width": 3,
1051
- "stroke-linecap": "round",
1052
- "stroke-linejoin": "round"
1053
- })
1054
- )
1055
- );
1056
- }
1057
1322
  function stepPulse(brand) {
1058
1323
  return el("div", {
1059
1324
  className: "dpdpa-step-icon dpdpa-step-icon-active",
1060
1325
  style: { backgroundColor: brand.primary }
1061
1326
  });
1062
1327
  }
1063
- var STEP_ORDER = {
1064
- pending: 0,
1065
- researching: 0,
1066
- analysis_complete: 2
1067
- };
1068
- var STEPS = [
1069
- { label: (company) => `Researching ${company}` },
1070
- { label: () => "Analysing DPDPA compliance" },
1071
- { label: () => "Generating your report" }
1072
- ];
1073
1328
  function renderPolling(brand, opts) {
1074
1329
  const pct = Math.min(100, Math.round(opts.elapsedMs / opts.pollTimeoutMs * 100));
1075
1330
  const progressFill = el("div", {
@@ -1081,92 +1336,191 @@ function renderPolling(brand, opts) {
1081
1336
  {
1082
1337
  className: "dpdpa-progress",
1083
1338
  role: "progressbar",
1084
- "aria-label": "Report generation progress",
1339
+ "aria-label": "Quick insights progress",
1085
1340
  "aria-valuemin": "0",
1086
1341
  "aria-valuemax": "100",
1087
1342
  "aria-valuenow": String(pct)
1088
1343
  },
1089
1344
  progressFill
1090
1345
  );
1091
- const currentIdx = STEP_ORDER[opts.subStatus];
1092
- const stepsList = el("div", { className: "dpdpa-steps" });
1093
- STEPS.forEach((step, idx) => {
1094
- if (idx > currentIdx) return;
1095
- const isActive = idx === currentIdx;
1096
- const row = el(
1097
- "div",
1098
- {
1099
- className: `dpdpa-step ${isActive ? "dpdpa-step-active" : "dpdpa-step-done"}`
1100
- },
1101
- isActive ? stepPulse(brand) : stepCheck(brand),
1102
- el(
1103
- "span",
1104
- {
1105
- className: isActive ? "dpdpa-step-label dpdpa-shimmer" : "dpdpa-step-label"
1106
- },
1107
- step.label(opts.companyName)
1108
- )
1109
- );
1110
- if (isActive) row.appendChild(animatedDots());
1111
- stepsList.appendChild(row);
1112
- });
1346
+ const stepRow = el(
1347
+ "div",
1348
+ { className: "dpdpa-step dpdpa-step-active" },
1349
+ stepPulse(brand),
1350
+ el(
1351
+ "span",
1352
+ { className: "dpdpa-step-label dpdpa-shimmer" },
1353
+ `Scanning ${opts.companyName}'s privacy footprint`
1354
+ ),
1355
+ animatedDots()
1356
+ );
1113
1357
  const body = el(
1114
1358
  "div",
1115
1359
  { className: "dpdpa-polling" },
1116
- stepsList,
1360
+ el("div", { className: "dpdpa-steps" }, stepRow),
1117
1361
  el(
1118
1362
  "p",
1119
1363
  { className: "dpdpa-polling-sub" },
1120
- "This usually takes 60 to 120 seconds. Keep this tab open."
1364
+ "Generating your preliminary DPDPA scan. This usually takes 10\u201320 seconds."
1121
1365
  ),
1122
1366
  progressBar
1123
1367
  );
1124
1368
  return el("div", { className: "dpdpa-card" }, header(brand), body, footer(brand));
1125
1369
  }
1126
- function checkIcon(brand) {
1370
+ function chip(text, variant) {
1371
+ return el(
1372
+ "span",
1373
+ { className: `dpdpa-chip${variant ? ` dpdpa-chip-${variant}` : ""}` },
1374
+ text
1375
+ );
1376
+ }
1377
+ function applicabilityBadge(level, reason) {
1378
+ const upper = (level || "").toUpperCase();
1379
+ let cls = "dpdpa-badge-neutral";
1380
+ if (upper.includes("VERY_LIKELY")) cls = "dpdpa-badge-strong";
1381
+ else if (upper === "LIKELY") cls = "dpdpa-badge-warn";
1382
+ else if (upper === "POSSIBLY") cls = "dpdpa-badge-soft";
1383
+ else if (upper === "UNLIKELY") cls = "dpdpa-badge-mute";
1127
1384
  return el(
1128
1385
  "div",
1129
- {
1130
- className: "dpdpa-check-circle",
1131
- style: { backgroundColor: brand.successColor }
1132
- },
1133
- svg(
1134
- "svg",
1135
- {
1136
- width: 28,
1137
- height: 28,
1138
- viewBox: "0 0 24 24",
1139
- fill: "none"
1140
- },
1141
- svg("path", {
1142
- d: "M5 12l5 5L20 7",
1143
- stroke: "#ffffff",
1144
- "stroke-width": 3,
1145
- "stroke-linecap": "round",
1146
- "stroke-linejoin": "round"
1147
- })
1148
- )
1386
+ { className: `dpdpa-applicability ${cls}` },
1387
+ el("span", { className: "dpdpa-applicability-level" }, level.replace(/_/g, " ")),
1388
+ el("span", { className: "dpdpa-applicability-reason" }, reason)
1149
1389
  );
1150
1390
  }
1151
- function isSafeUrl(raw) {
1152
- const s = (raw || "").trim();
1153
- if (!s) return false;
1154
- if (/^https?:\/\//i.test(s)) return true;
1155
- if (/^\/[^/]/.test(s) || s.startsWith("./") || s.startsWith("../"))
1156
- return true;
1157
- return false;
1391
+ function sectionTitle(text) {
1392
+ return el("h4", { className: "dpdpa-section-title" }, text);
1158
1393
  }
1159
- function renderComplete(brand, opts) {
1160
- const safeHref = isSafeUrl(opts.reportUrl) ? opts.reportUrl : "#";
1161
- const downloadBtn = el(
1162
- "a",
1163
- {
1164
- href: safeHref,
1165
- target: "_blank",
1166
- rel: "noopener noreferrer",
1167
- className: "dpdpa-btn-primary dpdpa-btn-link"
1168
- },
1169
- "Download PDF"
1394
+ function areaCard(area) {
1395
+ const refs = el(
1396
+ "div",
1397
+ { className: "dpdpa-chip-row" },
1398
+ ...area.dpdpa_references.map((r) => chip(r, "ref"))
1399
+ );
1400
+ const hint = area.redacto_product_hint ? el(
1401
+ "span",
1402
+ { className: "dpdpa-product-hint" },
1403
+ `Redacto ${area.redacto_product_hint}`
1404
+ ) : null;
1405
+ const heading = el(
1406
+ "div",
1407
+ { className: "dpdpa-area-heading" },
1408
+ el("h5", { className: "dpdpa-area-name" }, area.area),
1409
+ ...hint ? [hint] : []
1410
+ );
1411
+ return el(
1412
+ "div",
1413
+ { className: "dpdpa-area-card" },
1414
+ heading,
1415
+ refs,
1416
+ el("p", { className: "dpdpa-area-why" }, area.why_this_is_being_checked)
1417
+ );
1418
+ }
1419
+ function emailIcon() {
1420
+ return svg(
1421
+ "svg",
1422
+ { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none" },
1423
+ svg("path", {
1424
+ d: "M4 6h16v12H4z",
1425
+ stroke: "currentColor",
1426
+ "stroke-width": 2,
1427
+ "stroke-linejoin": "round"
1428
+ }),
1429
+ svg("path", {
1430
+ d: "M4 7l8 6 8-6",
1431
+ stroke: "currentColor",
1432
+ "stroke-width": 2,
1433
+ "stroke-linecap": "round",
1434
+ "stroke-linejoin": "round"
1435
+ })
1436
+ );
1437
+ }
1438
+ function renderQuickInsights(brand, opts) {
1439
+ const ins = opts.insights;
1440
+ const title = el(
1441
+ "h3",
1442
+ { className: "dpdpa-qi-title" },
1443
+ `Preliminary DPDPA scan for ${opts.companyName}`
1444
+ );
1445
+ const subtitle = el(
1446
+ "p",
1447
+ { className: "dpdpa-qi-subtitle" },
1448
+ ins.summary
1449
+ );
1450
+ const confidence = el(
1451
+ "div",
1452
+ { className: "dpdpa-confidence-row" },
1453
+ el("span", { className: "dpdpa-meta-label" }, "Confidence"),
1454
+ chip(ins.confidence)
1455
+ );
1456
+ const applicability = applicabilityBadge(
1457
+ ins.dpdpa_applicability_signal.level,
1458
+ ins.dpdpa_applicability_signal.reason
1459
+ );
1460
+ const eduNote = el(
1461
+ "div",
1462
+ { className: "dpdpa-callout" },
1463
+ el("div", { className: "dpdpa-callout-title" }, ins.educational_note.title),
1464
+ el("p", { className: "dpdpa-callout-message" }, ins.educational_note.message)
1465
+ );
1466
+ const areasSection = el(
1467
+ "section",
1468
+ { className: "dpdpa-section" },
1469
+ sectionTitle("Areas under review"),
1470
+ el(
1471
+ "div",
1472
+ { className: "dpdpa-area-list" },
1473
+ ...ins.areas_under_review.map((a) => areaCard(a))
1474
+ )
1475
+ );
1476
+ const cu = ins.company_understanding;
1477
+ const companySection = el(
1478
+ "section",
1479
+ { className: "dpdpa-section" },
1480
+ sectionTitle("Company understanding"),
1481
+ el("p", { className: "dpdpa-cu-profile" }, cu.business_profile),
1482
+ el(
1483
+ "div",
1484
+ { className: "dpdpa-cu-meta" },
1485
+ el("span", { className: "dpdpa-meta-label" }, "Likely users"),
1486
+ el("span", { className: "dpdpa-meta-value" }, cu.likely_user_relationship)
1487
+ ),
1488
+ el(
1489
+ "div",
1490
+ { className: "dpdpa-chip-row" },
1491
+ ...cu.likely_digital_touchpoints.map((t) => chip(t, "touchpoint"))
1492
+ )
1493
+ );
1494
+ const dataSection = el(
1495
+ "section",
1496
+ { className: "dpdpa-section" },
1497
+ sectionTitle("Data likely to validate"),
1498
+ el(
1499
+ "div",
1500
+ { className: "dpdpa-chip-row" },
1501
+ ...ins.likely_data_to_validate.map((d) => chip(d, "data"))
1502
+ )
1503
+ );
1504
+ const emailBanner = el(
1505
+ "div",
1506
+ { className: "dpdpa-email-banner" },
1507
+ el("span", { className: "dpdpa-email-icon" }, emailIcon()),
1508
+ el(
1509
+ "div",
1510
+ { className: "dpdpa-email-text" },
1511
+ el(
1512
+ "strong",
1513
+ { className: "dpdpa-email-headline" },
1514
+ "Your full DPDPA report is on its way."
1515
+ ),
1516
+ el(
1517
+ "p",
1518
+ { className: "dpdpa-email-sub" },
1519
+ `We'll email it to `,
1520
+ el("strong", null, opts.email),
1521
+ ` once compliance validation finishes.`
1522
+ )
1523
+ )
1170
1524
  );
1171
1525
  const resetLink = el(
1172
1526
  "a",
@@ -1178,26 +1532,33 @@ function renderComplete(brand, opts) {
1178
1532
  opts.onReset();
1179
1533
  }
1180
1534
  },
1181
- "Request another report"
1535
+ "Run another scan"
1182
1536
  );
1183
1537
  const body = el(
1184
1538
  "div",
1185
- { className: "dpdpa-success" },
1186
- checkIcon(brand),
1187
- el(
1188
- "h3",
1189
- { className: "dpdpa-success-title" },
1190
- `Your DPDPA report for ${opts.companyName} is ready`
1191
- ),
1539
+ { className: "dpdpa-qi" },
1192
1540
  el(
1193
- "p",
1194
- { className: "dpdpa-success-sub" },
1195
- "Click below to download your compliance assessment PDF."
1541
+ "div",
1542
+ { className: "dpdpa-qi-header" },
1543
+ title,
1544
+ applicability
1196
1545
  ),
1197
- downloadBtn,
1546
+ subtitle,
1547
+ confidence,
1548
+ eduNote,
1549
+ areasSection,
1550
+ companySection,
1551
+ dataSection,
1552
+ emailBanner,
1198
1553
  resetLink
1199
1554
  );
1200
- return el("div", { className: "dpdpa-card" }, header(brand), body, footer(brand));
1555
+ return el(
1556
+ "div",
1557
+ { className: "dpdpa-card dpdpa-card-wide" },
1558
+ header(brand),
1559
+ body,
1560
+ footer(brand)
1561
+ );
1201
1562
  }
1202
1563
  function renderError(brand, opts) {
1203
1564
  const retryBtn = el(
@@ -1236,7 +1597,6 @@ var FormController = class {
1236
1597
  this.pollHiddenSince = null;
1237
1598
  this.pollHiddenTotalMs = 0;
1238
1599
  this.pollResumePending = false;
1239
- this.lastSubStatus = null;
1240
1600
  this.consecutiveErrors = 0;
1241
1601
  this.destroyed = false;
1242
1602
  this.unsubscribe = null;
@@ -1249,7 +1609,7 @@ var FormController = class {
1249
1609
  subStatus: "pending",
1250
1610
  elapsedMs: 0,
1251
1611
  jobId: null,
1252
- reportUrl: null,
1612
+ quickInsights: null,
1253
1613
  errors: {},
1254
1614
  generalError: null,
1255
1615
  errorMessage: "",
@@ -1335,10 +1695,11 @@ var FormController = class {
1335
1695
  pollTimeoutMs: this.getPollTimeout()
1336
1696
  });
1337
1697
  break;
1338
- case "complete":
1339
- node = renderComplete(this.brand, {
1698
+ case "quick_insights":
1699
+ node = renderQuickInsights(this.brand, {
1340
1700
  companyName: this.fields.company_name,
1341
- reportUrl: s.reportUrl || "#",
1701
+ email: this.fields.email,
1702
+ insights: s.quickInsights,
1342
1703
  onReset: () => this.resetToIdle()
1343
1704
  });
1344
1705
  break;
@@ -1346,7 +1707,7 @@ var FormController = class {
1346
1707
  node = renderError(this.brand, {
1347
1708
  message: s.errorMessage,
1348
1709
  retryable: s.retryable,
1349
- onRetry: () => this.submit(),
1710
+ onRetry: () => this.retry(),
1350
1711
  onBack: () => {
1351
1712
  var _a;
1352
1713
  return this.backToIdle((_a = s.generalError) != null ? _a : s.errorMessage);
@@ -1446,7 +1807,8 @@ var FormController = class {
1446
1807
  baseUrl: this.getBaseUrl(),
1447
1808
  body: {
1448
1809
  company_name: lead.company_name,
1449
- company_domain: lead.company_domain
1810
+ company_domain: lead.company_domain,
1811
+ requested_by: lead.email
1450
1812
  }
1451
1813
  });
1452
1814
  if (this.destroyed) return;
@@ -1479,7 +1841,6 @@ var FormController = class {
1479
1841
  this.pollStartedAt = Date.now();
1480
1842
  this.pollHiddenSince = null;
1481
1843
  this.pollHiddenTotalMs = 0;
1482
- this.lastSubStatus = null;
1483
1844
  this.consecutiveErrors = 0;
1484
1845
  this.store.setState({
1485
1846
  phase: "polling",
@@ -1501,18 +1862,19 @@ var FormController = class {
1501
1862
  this.pollTimer = setTimeout(() => this.pollTick(), interval);
1502
1863
  }
1503
1864
  getActiveElapsed() {
1504
- return Date.now() - this.pollStartedAt - this.pollHiddenTotalMs;
1865
+ const hiddenInFlight = this.pollHiddenSince !== null ? Date.now() - this.pollHiddenSince : 0;
1866
+ return Date.now() - this.pollStartedAt - this.pollHiddenTotalMs - hiddenInFlight;
1505
1867
  }
1506
1868
  async pollTick() {
1507
- var _a, _b, _c, _d;
1869
+ var _a, _b;
1508
1870
  if (this.destroyed) return;
1509
1871
  const jobId = this.store.getState().jobId;
1510
1872
  if (!jobId) return;
1511
1873
  const activeElapsed = this.getActiveElapsed();
1512
1874
  if (activeElapsed >= this.getPollTimeout()) {
1513
1875
  this.showError(
1514
- "Report generation is taking longer than expected. Our team has been notified.",
1515
- false
1876
+ "Quick insights are taking longer than expected. Please try again.",
1877
+ true
1516
1878
  );
1517
1879
  this.emitError("Polling timed out", "timeout");
1518
1880
  return;
@@ -1522,33 +1884,47 @@ var FormController = class {
1522
1884
  if (this.destroyed) return;
1523
1885
  this.consecutiveErrors = 0;
1524
1886
  if ("status" in res && res.status === "error") {
1525
- this.showError(res.message || "Report generation failed.", true);
1526
- this.emitError(res.message || "Job errored", "poll");
1887
+ const errShape = res;
1888
+ this.showError(errShape.message || "Report generation failed.", true);
1889
+ this.emitError(errShape.message || "Job errored", "poll");
1527
1890
  return;
1528
1891
  }
1529
- if (res.status === "complete" && "report_url" in res && res.report_url) {
1892
+ const inProgress = res;
1893
+ if (inProgress.quick_insights_status === "complete") {
1894
+ const insights = inProgress.quick_insights;
1895
+ if (!insights) {
1896
+ this.showError(
1897
+ "Quick insights are marked complete but the payload is missing. Please try again.",
1898
+ true
1899
+ );
1900
+ this.emitError(
1901
+ "Quick insights complete without payload",
1902
+ "poll"
1903
+ );
1904
+ return;
1905
+ }
1530
1906
  const acceptedLead = this.makeLead(jobId);
1531
1907
  saveLead(acceptedLead);
1532
1908
  this.store.setState({
1533
- phase: "complete",
1534
- reportUrl: res.report_url
1909
+ phase: "quick_insights",
1910
+ quickInsights: insights
1535
1911
  });
1536
1912
  try {
1537
- (_b = (_a = this.options).onComplete) == null ? void 0 : _b.call(_a, res.report_url, acceptedLead);
1913
+ (_b = (_a = this.options).onQuickInsightsReady) == null ? void 0 : _b.call(_a, insights, acceptedLead);
1538
1914
  } catch (err) {
1539
- console.error("onComplete handler threw:", err);
1915
+ console.error("onQuickInsightsReady handler threw:", err);
1540
1916
  }
1541
1917
  return;
1542
1918
  }
1543
- const sub = res.status;
1544
- if (sub !== this.lastSubStatus) {
1545
- this.lastSubStatus = sub;
1546
- try {
1547
- (_d = (_c = this.options).onStatusChange) == null ? void 0 : _d.call(_c, sub);
1548
- } catch (err) {
1549
- console.error("onStatusChange handler threw:", err);
1550
- }
1919
+ if (inProgress.quick_insights_status === "error") {
1920
+ this.showError(
1921
+ "We couldn't generate quick insights. Please try again.",
1922
+ true
1923
+ );
1924
+ this.emitError("Quick insights errored", "poll");
1925
+ return;
1551
1926
  }
1927
+ const sub = (inProgress.status === "complete" ? "analysis_complete" : inProgress.status) || "pending";
1552
1928
  this.store.setState({
1553
1929
  phase: "polling",
1554
1930
  subStatus: sub,
@@ -1582,8 +1958,13 @@ var FormController = class {
1582
1958
  handleVisibilityChange() {
1583
1959
  if (this.destroyed) return;
1584
1960
  if (document.hidden) {
1585
- if (this.store.getState().phase === "polling" && this.pollHiddenSince === null) {
1586
- this.pollHiddenSince = Date.now();
1961
+ if (this.store.getState().phase === "polling") {
1962
+ if (this.pollHiddenSince === null) this.pollHiddenSince = Date.now();
1963
+ if (this.pollTimer) {
1964
+ clearTimeout(this.pollTimer);
1965
+ this.pollTimer = null;
1966
+ }
1967
+ this.pollResumePending = true;
1587
1968
  }
1588
1969
  return;
1589
1970
  }
@@ -1603,15 +1984,25 @@ var FormController = class {
1603
1984
  retryable
1604
1985
  });
1605
1986
  }
1987
+ // Resume polling on the existing job_id when we already have one;
1988
+ // otherwise re-POST. Prevents duplicate email delivery when a transient
1989
+ // poll/timeout error is retried.
1990
+ retry() {
1991
+ const jobId = this.store.getState().jobId;
1992
+ if (jobId) {
1993
+ this.startPolling(jobId);
1994
+ } else {
1995
+ this.submit();
1996
+ }
1997
+ }
1606
1998
  resetToIdle() {
1607
1999
  this.fields = { ...EMPTY_FIELDS };
1608
- this.lastSubStatus = null;
1609
2000
  this.store.setState({
1610
2001
  phase: "idle",
1611
2002
  errors: {},
1612
2003
  generalError: null,
1613
2004
  errorMessage: "",
1614
- reportUrl: null,
2005
+ quickInsights: null,
1615
2006
  jobId: null,
1616
2007
  subStatus: "pending",
1617
2008
  elapsedMs: 0
@@ -1653,7 +2044,7 @@ var FormController = class {
1653
2044
  };
1654
2045
 
1655
2046
  // src/index.ts
1656
- var version = "0.1.0";
2047
+ var version = "0.3.0";
1657
2048
  var activeController = null;
1658
2049
  function init(options) {
1659
2050
  destroy();