@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.
@@ -5,7 +5,7 @@ var PATHS = {
5
5
  status: "/webhook/dpdpa/status"
6
6
  };
7
7
  var DEFAULT_POLL_INTERVAL_MS = 3e3;
8
- var DEFAULT_POLL_TIMEOUT_MS = 3e5;
8
+ var DEFAULT_POLL_TIMEOUT_MS = 9e4;
9
9
  var MAX_CONSECUTIVE_ERRORS = 3;
10
10
  var REQUEST_TIMEOUT_MS = 15e3;
11
11
  var STORAGE_KEY = "redacto_dpdpa_lead";
@@ -707,6 +707,291 @@ function buildCss(brand) {
707
707
  from { transform: rotate(0deg); }
708
708
  to { transform: rotate(360deg); }
709
709
  }
710
+
711
+ /* Quick insights view */
712
+
713
+ .dpdpa-card-wide {
714
+ min-height: 0;
715
+ }
716
+
717
+ .dpdpa-qi {
718
+ display: flex;
719
+ flex-direction: column;
720
+ gap: 18px;
721
+ flex: 1;
722
+ animation: dpdpa-fade-in 0.35s ease-out both;
723
+ }
724
+
725
+ .dpdpa-qi-header {
726
+ display: flex;
727
+ flex-direction: column;
728
+ gap: 12px;
729
+ }
730
+
731
+ .dpdpa-qi-title {
732
+ margin: 0;
733
+ font-size: 18px;
734
+ font-weight: 700;
735
+ color: ${brand.text};
736
+ line-height: 1.3;
737
+ }
738
+
739
+ .dpdpa-qi-subtitle {
740
+ margin: 0;
741
+ font-size: 14px;
742
+ color: ${brand.text};
743
+ opacity: 0.85;
744
+ line-height: 1.55;
745
+ }
746
+
747
+ .dpdpa-applicability {
748
+ display: flex;
749
+ flex-direction: column;
750
+ gap: 4px;
751
+ padding: 12px 14px;
752
+ border-radius: ${brand.borderRadius};
753
+ border: 1px solid ${brand.border};
754
+ background: #ffffff;
755
+ }
756
+
757
+ .dpdpa-applicability-level {
758
+ font-size: 11px;
759
+ font-weight: 700;
760
+ letter-spacing: 0.6px;
761
+ text-transform: uppercase;
762
+ }
763
+
764
+ .dpdpa-applicability-reason {
765
+ font-size: 13px;
766
+ color: ${brand.text};
767
+ opacity: 0.85;
768
+ line-height: 1.45;
769
+ }
770
+
771
+ .dpdpa-badge-strong { border-color: ${brand.primary}66; background: ${brand.primary}10; }
772
+ .dpdpa-badge-strong .dpdpa-applicability-level { color: ${brand.primary}; }
773
+
774
+ .dpdpa-badge-warn { border-color: #f59e0b66; background: #f59e0b14; }
775
+ .dpdpa-badge-warn .dpdpa-applicability-level { color: #b45309; }
776
+
777
+ .dpdpa-badge-soft { border-color: #94a3b866; background: #94a3b814; }
778
+ .dpdpa-badge-soft .dpdpa-applicability-level { color: #475569; }
779
+
780
+ .dpdpa-badge-mute { border-color: ${brand.border}; background: ${brand.background}; }
781
+ .dpdpa-badge-mute .dpdpa-applicability-level { color: ${brand.text}; opacity: 0.7; }
782
+
783
+ .dpdpa-badge-neutral { border-color: ${brand.border}; background: ${brand.background}; }
784
+ .dpdpa-badge-neutral .dpdpa-applicability-level { color: ${brand.text}; }
785
+
786
+ .dpdpa-confidence-row {
787
+ display: flex;
788
+ align-items: center;
789
+ gap: 8px;
790
+ }
791
+
792
+ .dpdpa-meta-label {
793
+ font-size: 11px;
794
+ font-weight: 700;
795
+ letter-spacing: 0.5px;
796
+ text-transform: uppercase;
797
+ color: ${brand.text};
798
+ opacity: 0.6;
799
+ }
800
+
801
+ .dpdpa-meta-value {
802
+ font-size: 13px;
803
+ color: ${brand.text};
804
+ }
805
+
806
+ .dpdpa-chip {
807
+ display: inline-flex;
808
+ align-items: center;
809
+ padding: 3px 8px;
810
+ font-size: 11px;
811
+ font-weight: 600;
812
+ color: ${brand.text};
813
+ background: ${brand.background};
814
+ border: 1px solid ${brand.border};
815
+ border-radius: 999px;
816
+ white-space: nowrap;
817
+ }
818
+
819
+ .dpdpa-chip-ref {
820
+ background: ${brand.primary}12;
821
+ border-color: ${brand.primary}33;
822
+ color: ${brand.primary};
823
+ }
824
+
825
+ .dpdpa-chip-data {
826
+ background: #ecfeff;
827
+ border-color: #67e8f933;
828
+ color: #0e7490;
829
+ }
830
+
831
+ .dpdpa-chip-touchpoint {
832
+ background: #f5f3ff;
833
+ border-color: #c4b5fd33;
834
+ color: #5b21b6;
835
+ }
836
+
837
+ .dpdpa-chip-row {
838
+ display: flex;
839
+ flex-wrap: wrap;
840
+ gap: 6px;
841
+ }
842
+
843
+ .dpdpa-callout {
844
+ padding: 12px 14px;
845
+ background: ${brand.primary}08;
846
+ border: 1px solid ${brand.primary}22;
847
+ border-radius: ${brand.borderRadius};
848
+ display: flex;
849
+ flex-direction: column;
850
+ gap: 4px;
851
+ }
852
+
853
+ .dpdpa-callout-title {
854
+ font-size: 12px;
855
+ font-weight: 700;
856
+ letter-spacing: 0.3px;
857
+ text-transform: uppercase;
858
+ color: ${brand.primary};
859
+ }
860
+
861
+ .dpdpa-callout-message {
862
+ margin: 0;
863
+ font-size: 13px;
864
+ color: ${brand.text};
865
+ line-height: 1.5;
866
+ opacity: 0.85;
867
+ }
868
+
869
+ .dpdpa-section {
870
+ display: flex;
871
+ flex-direction: column;
872
+ gap: 10px;
873
+ }
874
+
875
+ .dpdpa-section-title {
876
+ margin: 0;
877
+ font-size: 13px;
878
+ font-weight: 700;
879
+ color: ${brand.text};
880
+ letter-spacing: 0.2px;
881
+ }
882
+
883
+ .dpdpa-area-list {
884
+ display: flex;
885
+ flex-direction: column;
886
+ gap: 10px;
887
+ }
888
+
889
+ .dpdpa-area-card {
890
+ padding: 12px 14px;
891
+ background: #ffffff;
892
+ border: 1px solid ${brand.border};
893
+ border-radius: ${brand.borderRadius};
894
+ display: flex;
895
+ flex-direction: column;
896
+ gap: 8px;
897
+ }
898
+
899
+ .dpdpa-area-heading {
900
+ display: flex;
901
+ align-items: center;
902
+ justify-content: space-between;
903
+ gap: 10px;
904
+ flex-wrap: wrap;
905
+ }
906
+
907
+ .dpdpa-area-name {
908
+ margin: 0;
909
+ font-size: 14px;
910
+ font-weight: 700;
911
+ color: ${brand.text};
912
+ }
913
+
914
+ .dpdpa-product-hint {
915
+ font-size: 11px;
916
+ font-weight: 600;
917
+ color: ${brand.primary};
918
+ background: ${brand.primary}12;
919
+ padding: 3px 8px;
920
+ border-radius: 999px;
921
+ white-space: nowrap;
922
+ }
923
+
924
+ .dpdpa-area-why {
925
+ margin: 0;
926
+ font-size: 13px;
927
+ color: ${brand.text};
928
+ opacity: 0.8;
929
+ line-height: 1.5;
930
+ }
931
+
932
+ .dpdpa-cu-profile {
933
+ margin: 0;
934
+ font-size: 13px;
935
+ color: ${brand.text};
936
+ opacity: 0.85;
937
+ line-height: 1.5;
938
+ }
939
+
940
+ .dpdpa-cu-meta {
941
+ display: flex;
942
+ align-items: center;
943
+ gap: 8px;
944
+ }
945
+
946
+ .dpdpa-email-banner {
947
+ display: flex;
948
+ align-items: flex-start;
949
+ gap: 12px;
950
+ padding: 14px 16px;
951
+ background: linear-gradient(135deg, ${brand.primary}10, ${brand.accent}10);
952
+ border: 1px solid ${brand.primary}33;
953
+ border-radius: ${brand.borderRadius};
954
+ }
955
+
956
+ .dpdpa-email-icon {
957
+ flex: none;
958
+ display: inline-flex;
959
+ align-items: center;
960
+ justify-content: center;
961
+ width: 32px;
962
+ height: 32px;
963
+ border-radius: 50%;
964
+ background: ${brand.primary};
965
+ color: ${brand.primaryText};
966
+ margin-top: 2px;
967
+ }
968
+
969
+ .dpdpa-email-text {
970
+ display: flex;
971
+ flex-direction: column;
972
+ gap: 2px;
973
+ min-width: 0;
974
+ }
975
+
976
+ .dpdpa-email-headline {
977
+ font-size: 14px;
978
+ font-weight: 700;
979
+ color: ${brand.text};
980
+ }
981
+
982
+ .dpdpa-email-sub {
983
+ margin: 0;
984
+ font-size: 13px;
985
+ color: ${brand.text};
986
+ opacity: 0.8;
987
+ line-height: 1.5;
988
+ word-break: break-word;
989
+ }
990
+
991
+ @keyframes dpdpa-fade-in {
992
+ from { opacity: 0; transform: translateY(6px); }
993
+ to { opacity: 1; transform: translateY(0); }
994
+ }
710
995
  `;
711
996
  }
712
997
  function injectFormStyles(root, brand) {
@@ -944,7 +1229,7 @@ function renderIdle(brand, fields, errors, generalErrorText, handlers) {
944
1229
  el(
945
1230
  "p",
946
1231
  { className: "dpdpa-subheading" },
947
- "We'll analyse your site and deliver a compliance assessment PDF."
1232
+ "We'll preview your DPDPA exposure here and email you the full report."
948
1233
  ),
949
1234
  generalError,
950
1235
  nameRow.row,
@@ -1004,42 +1289,12 @@ function animatedDots() {
1004
1289
  el("span")
1005
1290
  );
1006
1291
  }
1007
- function stepCheck(brand) {
1008
- return el(
1009
- "div",
1010
- {
1011
- className: "dpdpa-step-icon dpdpa-step-icon-done",
1012
- style: { backgroundColor: brand.successColor }
1013
- },
1014
- svg(
1015
- "svg",
1016
- { width: 12, height: 12, viewBox: "0 0 24 24", fill: "none" },
1017
- svg("path", {
1018
- d: "M5 12l5 5L20 7",
1019
- stroke: "#ffffff",
1020
- "stroke-width": 3,
1021
- "stroke-linecap": "round",
1022
- "stroke-linejoin": "round"
1023
- })
1024
- )
1025
- );
1026
- }
1027
1292
  function stepPulse(brand) {
1028
1293
  return el("div", {
1029
1294
  className: "dpdpa-step-icon dpdpa-step-icon-active",
1030
1295
  style: { backgroundColor: brand.primary }
1031
1296
  });
1032
1297
  }
1033
- var STEP_ORDER = {
1034
- pending: 0,
1035
- researching: 0,
1036
- analysis_complete: 2
1037
- };
1038
- var STEPS = [
1039
- { label: (company) => `Researching ${company}` },
1040
- { label: () => "Analysing DPDPA compliance" },
1041
- { label: () => "Generating your report" }
1042
- ];
1043
1298
  function renderPolling(brand, opts) {
1044
1299
  const pct = Math.min(100, Math.round(opts.elapsedMs / opts.pollTimeoutMs * 100));
1045
1300
  const progressFill = el("div", {
@@ -1051,92 +1306,191 @@ function renderPolling(brand, opts) {
1051
1306
  {
1052
1307
  className: "dpdpa-progress",
1053
1308
  role: "progressbar",
1054
- "aria-label": "Report generation progress",
1309
+ "aria-label": "Quick insights progress",
1055
1310
  "aria-valuemin": "0",
1056
1311
  "aria-valuemax": "100",
1057
1312
  "aria-valuenow": String(pct)
1058
1313
  },
1059
1314
  progressFill
1060
1315
  );
1061
- const currentIdx = STEP_ORDER[opts.subStatus];
1062
- const stepsList = el("div", { className: "dpdpa-steps" });
1063
- STEPS.forEach((step, idx) => {
1064
- if (idx > currentIdx) return;
1065
- const isActive = idx === currentIdx;
1066
- const row = el(
1067
- "div",
1068
- {
1069
- className: `dpdpa-step ${isActive ? "dpdpa-step-active" : "dpdpa-step-done"}`
1070
- },
1071
- isActive ? stepPulse(brand) : stepCheck(brand),
1072
- el(
1073
- "span",
1074
- {
1075
- className: isActive ? "dpdpa-step-label dpdpa-shimmer" : "dpdpa-step-label"
1076
- },
1077
- step.label(opts.companyName)
1078
- )
1079
- );
1080
- if (isActive) row.appendChild(animatedDots());
1081
- stepsList.appendChild(row);
1082
- });
1316
+ const stepRow = el(
1317
+ "div",
1318
+ { className: "dpdpa-step dpdpa-step-active" },
1319
+ stepPulse(brand),
1320
+ el(
1321
+ "span",
1322
+ { className: "dpdpa-step-label dpdpa-shimmer" },
1323
+ `Scanning ${opts.companyName}'s privacy footprint`
1324
+ ),
1325
+ animatedDots()
1326
+ );
1083
1327
  const body = el(
1084
1328
  "div",
1085
1329
  { className: "dpdpa-polling" },
1086
- stepsList,
1330
+ el("div", { className: "dpdpa-steps" }, stepRow),
1087
1331
  el(
1088
1332
  "p",
1089
1333
  { className: "dpdpa-polling-sub" },
1090
- "This usually takes 60 to 120 seconds. Keep this tab open."
1334
+ "Generating your preliminary DPDPA scan. This usually takes 10\u201320 seconds."
1091
1335
  ),
1092
1336
  progressBar
1093
1337
  );
1094
1338
  return el("div", { className: "dpdpa-card" }, header(brand), body, footer(brand));
1095
1339
  }
1096
- function checkIcon(brand) {
1340
+ function chip(text, variant) {
1341
+ return el(
1342
+ "span",
1343
+ { className: `dpdpa-chip${variant ? ` dpdpa-chip-${variant}` : ""}` },
1344
+ text
1345
+ );
1346
+ }
1347
+ function applicabilityBadge(level, reason) {
1348
+ const upper = (level || "").toUpperCase();
1349
+ let cls = "dpdpa-badge-neutral";
1350
+ if (upper.includes("VERY_LIKELY")) cls = "dpdpa-badge-strong";
1351
+ else if (upper === "LIKELY") cls = "dpdpa-badge-warn";
1352
+ else if (upper === "POSSIBLY") cls = "dpdpa-badge-soft";
1353
+ else if (upper === "UNLIKELY") cls = "dpdpa-badge-mute";
1097
1354
  return el(
1098
1355
  "div",
1099
- {
1100
- className: "dpdpa-check-circle",
1101
- style: { backgroundColor: brand.successColor }
1102
- },
1103
- svg(
1104
- "svg",
1105
- {
1106
- width: 28,
1107
- height: 28,
1108
- viewBox: "0 0 24 24",
1109
- fill: "none"
1110
- },
1111
- svg("path", {
1112
- d: "M5 12l5 5L20 7",
1113
- stroke: "#ffffff",
1114
- "stroke-width": 3,
1115
- "stroke-linecap": "round",
1116
- "stroke-linejoin": "round"
1117
- })
1118
- )
1356
+ { className: `dpdpa-applicability ${cls}` },
1357
+ el("span", { className: "dpdpa-applicability-level" }, level.replace(/_/g, " ")),
1358
+ el("span", { className: "dpdpa-applicability-reason" }, reason)
1119
1359
  );
1120
1360
  }
1121
- function isSafeUrl(raw) {
1122
- const s = (raw || "").trim();
1123
- if (!s) return false;
1124
- if (/^https?:\/\//i.test(s)) return true;
1125
- if (/^\/[^/]/.test(s) || s.startsWith("./") || s.startsWith("../"))
1126
- return true;
1127
- return false;
1361
+ function sectionTitle(text) {
1362
+ return el("h4", { className: "dpdpa-section-title" }, text);
1128
1363
  }
1129
- function renderComplete(brand, opts) {
1130
- const safeHref = isSafeUrl(opts.reportUrl) ? opts.reportUrl : "#";
1131
- const downloadBtn = el(
1132
- "a",
1133
- {
1134
- href: safeHref,
1135
- target: "_blank",
1136
- rel: "noopener noreferrer",
1137
- className: "dpdpa-btn-primary dpdpa-btn-link"
1138
- },
1139
- "Download PDF"
1364
+ function areaCard(area) {
1365
+ const refs = el(
1366
+ "div",
1367
+ { className: "dpdpa-chip-row" },
1368
+ ...area.dpdpa_references.map((r) => chip(r, "ref"))
1369
+ );
1370
+ const hint = area.redacto_product_hint ? el(
1371
+ "span",
1372
+ { className: "dpdpa-product-hint" },
1373
+ `Redacto ${area.redacto_product_hint}`
1374
+ ) : null;
1375
+ const heading = el(
1376
+ "div",
1377
+ { className: "dpdpa-area-heading" },
1378
+ el("h5", { className: "dpdpa-area-name" }, area.area),
1379
+ ...hint ? [hint] : []
1380
+ );
1381
+ return el(
1382
+ "div",
1383
+ { className: "dpdpa-area-card" },
1384
+ heading,
1385
+ refs,
1386
+ el("p", { className: "dpdpa-area-why" }, area.why_this_is_being_checked)
1387
+ );
1388
+ }
1389
+ function emailIcon() {
1390
+ return svg(
1391
+ "svg",
1392
+ { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none" },
1393
+ svg("path", {
1394
+ d: "M4 6h16v12H4z",
1395
+ stroke: "currentColor",
1396
+ "stroke-width": 2,
1397
+ "stroke-linejoin": "round"
1398
+ }),
1399
+ svg("path", {
1400
+ d: "M4 7l8 6 8-6",
1401
+ stroke: "currentColor",
1402
+ "stroke-width": 2,
1403
+ "stroke-linecap": "round",
1404
+ "stroke-linejoin": "round"
1405
+ })
1406
+ );
1407
+ }
1408
+ function renderQuickInsights(brand, opts) {
1409
+ const ins = opts.insights;
1410
+ const title = el(
1411
+ "h3",
1412
+ { className: "dpdpa-qi-title" },
1413
+ `Preliminary DPDPA scan for ${opts.companyName}`
1414
+ );
1415
+ const subtitle = el(
1416
+ "p",
1417
+ { className: "dpdpa-qi-subtitle" },
1418
+ ins.summary
1419
+ );
1420
+ const confidence = el(
1421
+ "div",
1422
+ { className: "dpdpa-confidence-row" },
1423
+ el("span", { className: "dpdpa-meta-label" }, "Confidence"),
1424
+ chip(ins.confidence)
1425
+ );
1426
+ const applicability = applicabilityBadge(
1427
+ ins.dpdpa_applicability_signal.level,
1428
+ ins.dpdpa_applicability_signal.reason
1429
+ );
1430
+ const eduNote = el(
1431
+ "div",
1432
+ { className: "dpdpa-callout" },
1433
+ el("div", { className: "dpdpa-callout-title" }, ins.educational_note.title),
1434
+ el("p", { className: "dpdpa-callout-message" }, ins.educational_note.message)
1435
+ );
1436
+ const areasSection = el(
1437
+ "section",
1438
+ { className: "dpdpa-section" },
1439
+ sectionTitle("Areas under review"),
1440
+ el(
1441
+ "div",
1442
+ { className: "dpdpa-area-list" },
1443
+ ...ins.areas_under_review.map((a) => areaCard(a))
1444
+ )
1445
+ );
1446
+ const cu = ins.company_understanding;
1447
+ const companySection = el(
1448
+ "section",
1449
+ { className: "dpdpa-section" },
1450
+ sectionTitle("Company understanding"),
1451
+ el("p", { className: "dpdpa-cu-profile" }, cu.business_profile),
1452
+ el(
1453
+ "div",
1454
+ { className: "dpdpa-cu-meta" },
1455
+ el("span", { className: "dpdpa-meta-label" }, "Likely users"),
1456
+ el("span", { className: "dpdpa-meta-value" }, cu.likely_user_relationship)
1457
+ ),
1458
+ el(
1459
+ "div",
1460
+ { className: "dpdpa-chip-row" },
1461
+ ...cu.likely_digital_touchpoints.map((t) => chip(t, "touchpoint"))
1462
+ )
1463
+ );
1464
+ const dataSection = el(
1465
+ "section",
1466
+ { className: "dpdpa-section" },
1467
+ sectionTitle("Data likely to validate"),
1468
+ el(
1469
+ "div",
1470
+ { className: "dpdpa-chip-row" },
1471
+ ...ins.likely_data_to_validate.map((d) => chip(d, "data"))
1472
+ )
1473
+ );
1474
+ const emailBanner = el(
1475
+ "div",
1476
+ { className: "dpdpa-email-banner" },
1477
+ el("span", { className: "dpdpa-email-icon" }, emailIcon()),
1478
+ el(
1479
+ "div",
1480
+ { className: "dpdpa-email-text" },
1481
+ el(
1482
+ "strong",
1483
+ { className: "dpdpa-email-headline" },
1484
+ "Your full DPDPA report is on its way."
1485
+ ),
1486
+ el(
1487
+ "p",
1488
+ { className: "dpdpa-email-sub" },
1489
+ `We'll email it to `,
1490
+ el("strong", null, opts.email),
1491
+ ` once compliance validation finishes.`
1492
+ )
1493
+ )
1140
1494
  );
1141
1495
  const resetLink = el(
1142
1496
  "a",
@@ -1148,26 +1502,33 @@ function renderComplete(brand, opts) {
1148
1502
  opts.onReset();
1149
1503
  }
1150
1504
  },
1151
- "Request another report"
1505
+ "Run another scan"
1152
1506
  );
1153
1507
  const body = el(
1154
1508
  "div",
1155
- { className: "dpdpa-success" },
1156
- checkIcon(brand),
1157
- el(
1158
- "h3",
1159
- { className: "dpdpa-success-title" },
1160
- `Your DPDPA report for ${opts.companyName} is ready`
1161
- ),
1509
+ { className: "dpdpa-qi" },
1162
1510
  el(
1163
- "p",
1164
- { className: "dpdpa-success-sub" },
1165
- "Click below to download your compliance assessment PDF."
1511
+ "div",
1512
+ { className: "dpdpa-qi-header" },
1513
+ title,
1514
+ applicability
1166
1515
  ),
1167
- downloadBtn,
1516
+ subtitle,
1517
+ confidence,
1518
+ eduNote,
1519
+ areasSection,
1520
+ companySection,
1521
+ dataSection,
1522
+ emailBanner,
1168
1523
  resetLink
1169
1524
  );
1170
- return el("div", { className: "dpdpa-card" }, header(brand), body, footer(brand));
1525
+ return el(
1526
+ "div",
1527
+ { className: "dpdpa-card dpdpa-card-wide" },
1528
+ header(brand),
1529
+ body,
1530
+ footer(brand)
1531
+ );
1171
1532
  }
1172
1533
  function renderError(brand, opts) {
1173
1534
  const retryBtn = el(
@@ -1206,7 +1567,6 @@ var FormController = class {
1206
1567
  this.pollHiddenSince = null;
1207
1568
  this.pollHiddenTotalMs = 0;
1208
1569
  this.pollResumePending = false;
1209
- this.lastSubStatus = null;
1210
1570
  this.consecutiveErrors = 0;
1211
1571
  this.destroyed = false;
1212
1572
  this.unsubscribe = null;
@@ -1219,7 +1579,7 @@ var FormController = class {
1219
1579
  subStatus: "pending",
1220
1580
  elapsedMs: 0,
1221
1581
  jobId: null,
1222
- reportUrl: null,
1582
+ quickInsights: null,
1223
1583
  errors: {},
1224
1584
  generalError: null,
1225
1585
  errorMessage: "",
@@ -1305,10 +1665,11 @@ var FormController = class {
1305
1665
  pollTimeoutMs: this.getPollTimeout()
1306
1666
  });
1307
1667
  break;
1308
- case "complete":
1309
- node = renderComplete(this.brand, {
1668
+ case "quick_insights":
1669
+ node = renderQuickInsights(this.brand, {
1310
1670
  companyName: this.fields.company_name,
1311
- reportUrl: s.reportUrl || "#",
1671
+ email: this.fields.email,
1672
+ insights: s.quickInsights,
1312
1673
  onReset: () => this.resetToIdle()
1313
1674
  });
1314
1675
  break;
@@ -1316,7 +1677,7 @@ var FormController = class {
1316
1677
  node = renderError(this.brand, {
1317
1678
  message: s.errorMessage,
1318
1679
  retryable: s.retryable,
1319
- onRetry: () => this.submit(),
1680
+ onRetry: () => this.retry(),
1320
1681
  onBack: () => {
1321
1682
  var _a;
1322
1683
  return this.backToIdle((_a = s.generalError) != null ? _a : s.errorMessage);
@@ -1416,7 +1777,8 @@ var FormController = class {
1416
1777
  baseUrl: this.getBaseUrl(),
1417
1778
  body: {
1418
1779
  company_name: lead.company_name,
1419
- company_domain: lead.company_domain
1780
+ company_domain: lead.company_domain,
1781
+ requested_by: lead.email
1420
1782
  }
1421
1783
  });
1422
1784
  if (this.destroyed) return;
@@ -1449,7 +1811,6 @@ var FormController = class {
1449
1811
  this.pollStartedAt = Date.now();
1450
1812
  this.pollHiddenSince = null;
1451
1813
  this.pollHiddenTotalMs = 0;
1452
- this.lastSubStatus = null;
1453
1814
  this.consecutiveErrors = 0;
1454
1815
  this.store.setState({
1455
1816
  phase: "polling",
@@ -1471,18 +1832,19 @@ var FormController = class {
1471
1832
  this.pollTimer = setTimeout(() => this.pollTick(), interval);
1472
1833
  }
1473
1834
  getActiveElapsed() {
1474
- return Date.now() - this.pollStartedAt - this.pollHiddenTotalMs;
1835
+ const hiddenInFlight = this.pollHiddenSince !== null ? Date.now() - this.pollHiddenSince : 0;
1836
+ return Date.now() - this.pollStartedAt - this.pollHiddenTotalMs - hiddenInFlight;
1475
1837
  }
1476
1838
  async pollTick() {
1477
- var _a, _b, _c, _d;
1839
+ var _a, _b;
1478
1840
  if (this.destroyed) return;
1479
1841
  const jobId = this.store.getState().jobId;
1480
1842
  if (!jobId) return;
1481
1843
  const activeElapsed = this.getActiveElapsed();
1482
1844
  if (activeElapsed >= this.getPollTimeout()) {
1483
1845
  this.showError(
1484
- "Report generation is taking longer than expected. Our team has been notified.",
1485
- false
1846
+ "Quick insights are taking longer than expected. Please try again.",
1847
+ true
1486
1848
  );
1487
1849
  this.emitError("Polling timed out", "timeout");
1488
1850
  return;
@@ -1492,33 +1854,47 @@ var FormController = class {
1492
1854
  if (this.destroyed) return;
1493
1855
  this.consecutiveErrors = 0;
1494
1856
  if ("status" in res && res.status === "error") {
1495
- this.showError(res.message || "Report generation failed.", true);
1496
- this.emitError(res.message || "Job errored", "poll");
1857
+ const errShape = res;
1858
+ this.showError(errShape.message || "Report generation failed.", true);
1859
+ this.emitError(errShape.message || "Job errored", "poll");
1497
1860
  return;
1498
1861
  }
1499
- if (res.status === "complete" && "report_url" in res && res.report_url) {
1862
+ const inProgress = res;
1863
+ if (inProgress.quick_insights_status === "complete") {
1864
+ const insights = inProgress.quick_insights;
1865
+ if (!insights) {
1866
+ this.showError(
1867
+ "Quick insights are marked complete but the payload is missing. Please try again.",
1868
+ true
1869
+ );
1870
+ this.emitError(
1871
+ "Quick insights complete without payload",
1872
+ "poll"
1873
+ );
1874
+ return;
1875
+ }
1500
1876
  const acceptedLead = this.makeLead(jobId);
1501
1877
  saveLead(acceptedLead);
1502
1878
  this.store.setState({
1503
- phase: "complete",
1504
- reportUrl: res.report_url
1879
+ phase: "quick_insights",
1880
+ quickInsights: insights
1505
1881
  });
1506
1882
  try {
1507
- (_b = (_a = this.options).onComplete) == null ? void 0 : _b.call(_a, res.report_url, acceptedLead);
1883
+ (_b = (_a = this.options).onQuickInsightsReady) == null ? void 0 : _b.call(_a, insights, acceptedLead);
1508
1884
  } catch (err) {
1509
- console.error("onComplete handler threw:", err);
1885
+ console.error("onQuickInsightsReady handler threw:", err);
1510
1886
  }
1511
1887
  return;
1512
1888
  }
1513
- const sub = res.status;
1514
- if (sub !== this.lastSubStatus) {
1515
- this.lastSubStatus = sub;
1516
- try {
1517
- (_d = (_c = this.options).onStatusChange) == null ? void 0 : _d.call(_c, sub);
1518
- } catch (err) {
1519
- console.error("onStatusChange handler threw:", err);
1520
- }
1889
+ if (inProgress.quick_insights_status === "error") {
1890
+ this.showError(
1891
+ "We couldn't generate quick insights. Please try again.",
1892
+ true
1893
+ );
1894
+ this.emitError("Quick insights errored", "poll");
1895
+ return;
1521
1896
  }
1897
+ const sub = (inProgress.status === "complete" ? "analysis_complete" : inProgress.status) || "pending";
1522
1898
  this.store.setState({
1523
1899
  phase: "polling",
1524
1900
  subStatus: sub,
@@ -1552,8 +1928,13 @@ var FormController = class {
1552
1928
  handleVisibilityChange() {
1553
1929
  if (this.destroyed) return;
1554
1930
  if (document.hidden) {
1555
- if (this.store.getState().phase === "polling" && this.pollHiddenSince === null) {
1556
- this.pollHiddenSince = Date.now();
1931
+ if (this.store.getState().phase === "polling") {
1932
+ if (this.pollHiddenSince === null) this.pollHiddenSince = Date.now();
1933
+ if (this.pollTimer) {
1934
+ clearTimeout(this.pollTimer);
1935
+ this.pollTimer = null;
1936
+ }
1937
+ this.pollResumePending = true;
1557
1938
  }
1558
1939
  return;
1559
1940
  }
@@ -1573,15 +1954,25 @@ var FormController = class {
1573
1954
  retryable
1574
1955
  });
1575
1956
  }
1957
+ // Resume polling on the existing job_id when we already have one;
1958
+ // otherwise re-POST. Prevents duplicate email delivery when a transient
1959
+ // poll/timeout error is retried.
1960
+ retry() {
1961
+ const jobId = this.store.getState().jobId;
1962
+ if (jobId) {
1963
+ this.startPolling(jobId);
1964
+ } else {
1965
+ this.submit();
1966
+ }
1967
+ }
1576
1968
  resetToIdle() {
1577
1969
  this.fields = { ...EMPTY_FIELDS };
1578
- this.lastSubStatus = null;
1579
1970
  this.store.setState({
1580
1971
  phase: "idle",
1581
1972
  errors: {},
1582
1973
  generalError: null,
1583
1974
  errorMessage: "",
1584
- reportUrl: null,
1975
+ quickInsights: null,
1585
1976
  jobId: null,
1586
1977
  subStatus: "pending",
1587
1978
  elapsedMs: 0
@@ -1623,7 +2014,7 @@ var FormController = class {
1623
2014
  };
1624
2015
 
1625
2016
  // src/index.ts
1626
- var version = "0.1.0";
2017
+ var version = "0.3.0";
1627
2018
  var activeController = null;
1628
2019
  function init(options) {
1629
2020
  destroy();