@paro.io/expert-shared-components 1.14.68 → 1.14.70

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.
@@ -55,4 +55,12 @@ export type TaxAxisApi = {
55
55
  generatePdf: (sessionId: string) => Promise<any>;
56
56
  getArtifacts: (sessionId: string) => Promise<any[]>;
57
57
  importQboReport?: (sessionId: string, expertId: string, year: number, reportType: string) => Promise<any>;
58
+ generateProspectReport?: (input: {
59
+ freelancerId?: number | null;
60
+ clientProfile: Record<string, unknown>;
61
+ }) => Promise<{
62
+ reportId: string;
63
+ computePayload: Record<string, unknown>;
64
+ outputPayload: Record<string, unknown>;
65
+ }>;
58
66
  };
@@ -147,6 +147,8 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
147
147
  const [busyMessage, setBusyMessage] = (0, react_1.useState)('Syncing Tax Axis session...');
148
148
  const [reportReady, setReportReady] = (0, react_1.useState)(false);
149
149
  const isPollingRef = react_1.default.useRef(false);
150
+ const [prospectLoading, setProspectLoading] = (0, react_1.useState)(false);
151
+ const [prospectData, setProspectData] = (0, react_1.useState)(null);
150
152
  // QBO state driven by EPS props — not localStorage
151
153
  const [qboConnectedState, setQboConnectedState] = (0, react_1.useState)(!!qboConnected);
152
154
  const [qboCompanyNameState, setQboCompanyNameState] = (0, react_1.useState)(qboCompanyName !== null && qboCompanyName !== void 0 ? qboCompanyName : null);
@@ -238,11 +240,30 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
238
240
  setReportReady(false);
239
241
  updateSessionId(null);
240
242
  }, [updateSessionId]);
241
- const handleProspect = (0, react_1.useCallback)((nextProfile) => {
243
+ const handleProspect = (nextProfile) => __awaiter(void 0, void 0, void 0, function* () {
242
244
  setProfile(nextProfile);
243
245
  setIsProspectFlow(true);
246
+ setProspectData(null);
247
+ setProspectLoading(true);
244
248
  setStep('PROSPECT_REPORT');
245
- }, []);
249
+ try {
250
+ if (taxAxisApi.generateProspectReport) {
251
+ const result = yield taxAxisApi.generateProspectReport({
252
+ clientProfile: nextProfile,
253
+ });
254
+ setProspectData({
255
+ computePayload: result.computePayload,
256
+ outputPayload: result.outputPayload,
257
+ });
258
+ }
259
+ }
260
+ catch (err) {
261
+ console.error('[TaxAxisShell] generateProspectReport failed', err);
262
+ }
263
+ finally {
264
+ setProspectLoading(false);
265
+ }
266
+ });
246
267
  const handleFullAnalysis = (0, react_1.useCallback)((nextProfile) => __awaiter(void 0, void 0, void 0, function* () {
247
268
  setProfile(nextProfile);
248
269
  setIsProspectFlow(false);
@@ -411,7 +432,7 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
411
432
  }
412
433
  }), [sessionId, taxAxisApi]);
413
434
  const currentView = (0, react_1.useMemo)(() => {
414
- var _a, _b, _c, _d;
435
+ var _a, _b, _c, _d, _e, _f;
415
436
  if (step === 'SESSION_SETUP') {
416
437
  return (react_1.default.createElement(ShellContainer, null,
417
438
  react_1.default.createElement(TaxAxisIntake_1.TaxAxisIntake, { userContext: userContext, initialProfile: initialProfile, onProspect: handleProspect, onFullAnalysis: handleFullAnalysis })));
@@ -422,8 +443,7 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
422
443
  }
423
444
  switch (step) {
424
445
  case 'PROSPECT_REPORT':
425
- return (react_1.default.createElement(ShellContainer, null,
426
- react_1.default.createElement(TaxAxisProspectReport_1.TaxAxisProspectReport, { profile: profile, userContext: userContext, onUpgrade: () => setStep('DOCUMENT_UPLOAD'), onPresent: () => setStep('PRESENTATION'), onReset: handleReset })));
446
+ return (react_1.default.createElement(ShellContainer, null, prospectLoading ? (react_1.default.createElement(TaxAxisProcessing_1.TaxAxisProcessing, { profile: profile, reportReady: false, userContext: userContext, crawlStep: 1.2, mode: "prospect", onComplete: () => { } })) : (react_1.default.createElement(TaxAxisProspectReport_1.TaxAxisProspectReport, { profile: profile, userContext: userContext, backendComputePayload: (_a = prospectData === null || prospectData === void 0 ? void 0 : prospectData.computePayload) !== null && _a !== void 0 ? _a : null, outputPayload: (_b = prospectData === null || prospectData === void 0 ? void 0 : prospectData.outputPayload) !== null && _b !== void 0 ? _b : null, onUpgrade: () => window.open('/tax-axis/documents', '_blank', 'noopener'), onPresent: () => setStep('PRESENTATION'), onReset: handleReset }))));
427
447
  case 'DOCUMENT_UPLOAD':
428
448
  return (react_1.default.createElement(ShellContainer, null,
429
449
  react_1.default.createElement("div", { className: "flex items-center justify-between rounded-lg px-4 py-3 mb-5", style: {
@@ -487,10 +507,10 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
487
507
  react_1.default.createElement(TaxAxisDashboard_1.TaxAxisDashboard, { profile: profile, userContext: userContext, llmResult: llmResult, parsedDocuments: parsedDocuments, onDownloadClient: () => setStep('CLIENT_REPORT'), onDownloadPreparer: () => setStep('PREPARER_WORKPAPER'), onPresent: () => setStep('PRESENTATION'), onSend: handleSendReport, onReviewData: () => setStep('PARSED_REVIEW'), onReset: handleReset })));
488
508
  case 'CLIENT_REPORT':
489
509
  return (react_1.default.createElement(ShellContainer, { fullWidth: true },
490
- react_1.default.createElement(TaxAxisClientReport_1.TaxAxisClientReport, { profile: profile, userContext: userContext, onBack: () => setStep('DASHBOARD'), onNavigatePreparer: () => setStep('PREPARER_WORKPAPER'), liveStrategies: adapted === null || adapted === void 0 ? void 0 : adapted.strategies, liveComputedMap: adapted === null || adapted === void 0 ? void 0 : adapted.computedMap, engineRawOutput: (_a = llmResult === null || llmResult === void 0 ? void 0 : llmResult.rawOutput) !== null && _a !== void 0 ? _a : llmResult === null || llmResult === void 0 ? void 0 : llmResult.engineOutput })));
510
+ react_1.default.createElement(TaxAxisClientReport_1.TaxAxisClientReport, { profile: profile, userContext: userContext, onBack: () => setStep('DASHBOARD'), onNavigatePreparer: () => setStep('PREPARER_WORKPAPER'), liveStrategies: adapted === null || adapted === void 0 ? void 0 : adapted.strategies, liveComputedMap: adapted === null || adapted === void 0 ? void 0 : adapted.computedMap, engineRawOutput: (_c = llmResult === null || llmResult === void 0 ? void 0 : llmResult.rawOutput) !== null && _c !== void 0 ? _c : llmResult === null || llmResult === void 0 ? void 0 : llmResult.engineOutput })));
491
511
  case 'PREPARER_WORKPAPER':
492
512
  return (react_1.default.createElement(ShellContainer, { fullWidth: true },
493
- react_1.default.createElement(TaxAxisPreparerWorkpaper_1.TaxAxisPreparerWorkpaper, { profile: profile, userContext: userContext, onBack: () => setStep('DASHBOARD'), onToggleToClient: () => setStep('CLIENT_REPORT'), liveStrategies: adapted === null || adapted === void 0 ? void 0 : adapted.strategies, liveComputedMap: adapted === null || adapted === void 0 ? void 0 : adapted.computedMap, cpaWorkflow: (_b = llmResult === null || llmResult === void 0 ? void 0 : llmResult.rawOutput) === null || _b === void 0 ? void 0 : _b.cpa_workflow, riskDisclosures: (_c = llmResult === null || llmResult === void 0 ? void 0 : llmResult.rawOutput) === null || _c === void 0 ? void 0 : _c.risk_disclosures, businessProfile: (_d = llmResult === null || llmResult === void 0 ? void 0 : llmResult.rawOutput) === null || _d === void 0 ? void 0 : _d.business_profile })));
513
+ react_1.default.createElement(TaxAxisPreparerWorkpaper_1.TaxAxisPreparerWorkpaper, { profile: profile, userContext: userContext, onBack: () => setStep('DASHBOARD'), onToggleToClient: () => setStep('CLIENT_REPORT'), liveStrategies: adapted === null || adapted === void 0 ? void 0 : adapted.strategies, liveComputedMap: adapted === null || adapted === void 0 ? void 0 : adapted.computedMap, cpaWorkflow: (_d = llmResult === null || llmResult === void 0 ? void 0 : llmResult.rawOutput) === null || _d === void 0 ? void 0 : _d.cpa_workflow, riskDisclosures: (_e = llmResult === null || llmResult === void 0 ? void 0 : llmResult.rawOutput) === null || _e === void 0 ? void 0 : _e.risk_disclosures, businessProfile: (_f = llmResult === null || llmResult === void 0 ? void 0 : llmResult.rawOutput) === null || _f === void 0 ? void 0 : _f.business_profile })));
494
514
  case 'PRESENTATION':
495
515
  return (react_1.default.createElement(ShellContainer, { fullWidth: true },
496
516
  react_1.default.createElement(TaxAxisPresentationMode_1.TaxAxisPresentationMode, { profile: profile, userContext: userContext, onBack: () => setStep(isProspectFlow ? 'PROSPECT_REPORT' : 'DASHBOARD') })));
@@ -1,9 +1,15 @@
1
1
  import React from "react";
2
- import { UserContext } from "../../lib/types";
2
+ import type { ClientProfile, UserContext } from "../../lib/types";
3
+ interface ProspectReadiness {
4
+ ready: boolean;
5
+ missingFields: string[];
6
+ }
7
+ export declare function isProspectReady(profile: Partial<ClientProfile>): ProspectReadiness;
3
8
  interface IntakeCtaCardsProps {
9
+ profile: Partial<ClientProfile>;
4
10
  onProspect: () => void;
5
11
  onFullAnalysis: () => void;
6
12
  userContext?: UserContext;
7
13
  }
8
- export declare function IntakeCtaCards({ onProspect, onFullAnalysis, userContext, }: IntakeCtaCardsProps): React.JSX.Element;
14
+ export declare function IntakeCtaCards({ profile, onProspect, onFullAnalysis, userContext, }: IntakeCtaCardsProps): React.JSX.Element;
9
15
  export {};
@@ -1,21 +1,84 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
4
24
  };
5
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.isProspectReady = isProspectReady;
6
27
  exports.IntakeCtaCards = IntakeCtaCards;
7
- const react_1 = __importDefault(require("react"));
28
+ const react_1 = __importStar(require("react"));
8
29
  const TaxAxisButton_1 = require("../shared/TaxAxisButton");
9
30
  const TaxAxisBadge_1 = require("../shared/TaxAxisBadge");
10
- function IntakeCtaCards({ onProspect, onFullAnalysis, userContext = "expert", }) {
31
+ function isProspectReady(profile) {
32
+ const missing = [];
33
+ if (!profile.entity)
34
+ missing.push("Entity Type");
35
+ if (!profile.states || profile.states.length === 0)
36
+ missing.push("States of Operation");
37
+ if (!profile.federalRate)
38
+ missing.push("Federal Marginal Rate");
39
+ if (!profile.stateRate)
40
+ missing.push("State Marginal Rate");
41
+ if (!profile.taxDataYears)
42
+ missing.push("Tax Data Years Available");
43
+ if (!profile.riskTolerance)
44
+ missing.push("Client Risk Tolerance");
45
+ const hasFinancial = parseFloat(profile.netIncome || "0") > 0 ||
46
+ parseFloat(profile.equipmentPurchased || "0") > 0 ||
47
+ parseFloat(profile.capitalGains || "0") > 0 ||
48
+ parseFloat(profile.revenue || "0") > 0;
49
+ if (!hasFinancial)
50
+ missing.push("Annual Revenue or Net Income (at least one > 0)");
51
+ return { ready: missing.length === 0, missingFields: missing };
52
+ }
53
+ function IntakeCtaCards({ profile, onProspect, onFullAnalysis, userContext = "expert", }) {
54
+ const [showTooltip, setShowTooltip] = (0, react_1.useState)(false);
55
+ const { ready, missingFields } = isProspectReady(profile);
11
56
  return (react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3" },
12
57
  react_1.default.createElement("div", { className: "bg-tax-axis-surface border border-tax-axis-border rounded-xl p-4 min-h-[140px] flex flex-col transition-all" },
13
58
  react_1.default.createElement("div", { className: "text-[11px] font-bold text-tax-axis-orange uppercase tracking-widest mb-2.5 font-tax-axis-body" }, "Prospect Report"),
14
59
  react_1.default.createElement("div", { className: "text-xs text-tax-axis-text-3 font-tax-axis-body mb-3" }, "Top 3 strategies \u00B7 Savings ranges \u00B7 ~2 min \u00B7 No docs"),
15
60
  react_1.default.createElement("div", { className: "flex-1" }),
16
- react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "orange", onClick: onProspect, className: "w-full text-xs" }, userContext === "cpa-firm-client"
17
- ? "Generate My Savings Report"
18
- : "Generate Prospect Report")),
61
+ react_1.default.createElement("div", { className: "relative", onMouseEnter: () => !ready && setShowTooltip(true), onMouseLeave: () => setShowTooltip(false) },
62
+ react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "orange", onClick: ready ? onProspect : undefined, className: `w-full text-xs ${!ready ? "opacity-40 cursor-not-allowed" : ""}`, disabled: !ready }, userContext === "cpa-firm-client"
63
+ ? "Generate My Savings Report"
64
+ : "Generate Prospect Report"),
65
+ !ready && showTooltip && (react_1.default.createElement("div", { style: {
66
+ position: "absolute",
67
+ bottom: "calc(100% + 8px)",
68
+ left: 0,
69
+ right: 0,
70
+ background: "#1a1f3d",
71
+ border: "1px solid #2a2f5d",
72
+ borderRadius: 8,
73
+ padding: "10px 12px",
74
+ zIndex: 50,
75
+ boxShadow: "0 4px 24px rgba(0,0,0,0.5)",
76
+ } },
77
+ react_1.default.createElement("div", { style: { fontSize: 10, fontWeight: 700, color: "#f97316", marginBottom: 6, textTransform: "uppercase", letterSpacing: "0.1em" } }, "Required to generate:"),
78
+ missingFields.map((f) => (react_1.default.createElement("div", { key: f, style: { fontSize: 11, color: "#94a3b8", marginBottom: 2, display: "flex", alignItems: "center", gap: 6 } },
79
+ react_1.default.createElement("span", { style: { color: "#f97316", fontSize: 9 } }, "\u25CF"),
80
+ " ",
81
+ f))))))),
19
82
  react_1.default.createElement("div", { className: "bg-tax-axis-surface border border-tax-axis-teal rounded-xl p-4 min-h-[140px] flex flex-col transition-all", style: {
20
83
  background: "radial-gradient(ellipse at 50% 0%,rgba(36,131,132,0.06) 0%,transparent 70%) #0F1330",
21
84
  boxShadow: "0 0 30px rgba(36,131,132,0.15)",
@@ -32,7 +32,7 @@ function TaxAxisIntake({ userContext = "expert", onProspect, onFullAnalysis, ini
32
32
  react_1.default.createElement(ClientParametersSection_1.ClientParametersSection, { userContext: userContext }),
33
33
  react_1.default.createElement(RefineAnalysisSection_1.RefineAnalysisSection, { userContext: userContext }),
34
34
  react_1.default.createElement(CpaIntakeQuestionsSection_1.CpaIntakeQuestionsSection, { userContext: userContext }),
35
- react_1.default.createElement(IntakeCtaCards_1.IntakeCtaCards, { onProspect: handleProspect, onFullAnalysis: handleFull, userContext: userContext })),
35
+ react_1.default.createElement(IntakeCtaCards_1.IntakeCtaCards, { profile: profile, onProspect: handleProspect, onFullAnalysis: handleFull, userContext: userContext })),
36
36
  react_1.default.createElement("div", { className: "hidden sm:block sm:sticky sm:top-5 sm:self-start" },
37
37
  react_1.default.createElement(StrategyRadar_1.StrategyRadar, { profile: profile })))));
38
38
  }
@@ -5,5 +5,9 @@ export interface TaxAxisProcessingProps extends TaxAxisScreenProps {
5
5
  profile?: ClientProfile;
6
6
  /** Set to true by the shell poller when stage===REPORT_READY is detected. */
7
7
  reportReady?: boolean;
8
+ /** Override crawl speed. Default: 0.27%/tick (full analysis ~110s). Prospect report: use ~6 for ~15s. */
9
+ crawlStep?: number;
10
+ /** 'prospect' shows lightweight stages without document parsing steps */
11
+ mode?: 'full' | 'prospect';
8
12
  }
9
- export declare function TaxAxisProcessing({ onComplete, profile, reportReady, userContext: _userContext, }: TaxAxisProcessingProps): React.JSX.Element;
13
+ export declare function TaxAxisProcessing({ onComplete, profile, reportReady, crawlStep, mode, userContext: _userContext, }: TaxAxisProcessingProps): React.JSX.Element;
@@ -31,8 +31,21 @@ const CRAWL_TICK_MS = 300; // interval between crawl ticks
31
31
  const CRAWL_STEP = 0.27; // % added each tick → 0.27 / 0.3s = 0.9%/s
32
32
  const FINISH_MS = 400; // ms to animate from 99% → 100% once ready
33
33
  const STAGE_ADVANCE_MS = 3200; // how often the displayed stage advances
34
- function buildStages(profile) {
34
+ function buildStages(profile, mode) {
35
35
  var _a;
36
+ const entity = (profile === null || profile === void 0 ? void 0 : profile.entity) || "your business";
37
+ const stateCount = ((_a = profile === null || profile === void 0 ? void 0 : profile.states) === null || _a === void 0 ? void 0 : _a.length) || 1;
38
+ const bizName = (profile === null || profile === void 0 ? void 0 : profile.bizName) || "your business";
39
+ if (mode === 'prospect') {
40
+ return [
41
+ `Classifying ${entity} profile`,
42
+ "Injecting IRS 2026 + OBBBA parameters",
43
+ `Screening 25 strategies across ${stateCount} state${stateCount > 1 ? "s" : ""}`,
44
+ "Computing deterministic tax math",
45
+ "Generating strategy narratives",
46
+ `Assembling prospect report for ${bizName}`,
47
+ ];
48
+ }
36
49
  if (!profile) {
37
50
  return [
38
51
  "Parsing source documents",
@@ -42,9 +55,6 @@ function buildStages(profile) {
42
55
  "Generating IRS citations",
43
56
  ];
44
57
  }
45
- const entity = profile.entity || "your business";
46
- const stateCount = ((_a = profile.states) === null || _a === void 0 ? void 0 : _a.length) || 1;
47
- const bizName = profile.bizName || "your business";
48
58
  const base = [
49
59
  "Parsing source documents",
50
60
  "Injecting IRS 2026 parameters",
@@ -60,10 +70,16 @@ function buildStages(profile) {
60
70
  base.push("Screening 30 eligible strategies", "Computing deterministic tax math", "Cross-validating source documents", "Generating IRS citations", `Assembling TaxAxis report for ${bizName}`);
61
71
  return base;
62
72
  }
63
- function TaxAxisProcessing({ onComplete, profile, reportReady = false, userContext: _userContext = "expert", }) {
64
- const stages = (0, react_1.useMemo)(() => buildStages(profile), [profile]);
73
+ function TaxAxisProcessing({ onComplete, profile, reportReady = false, crawlStep, mode, userContext: _userContext = "expert", }) {
74
+ const stages = (0, react_1.useMemo)(() => buildStages(profile, mode), [profile, mode]);
65
75
  const [progress, setProgress] = (0, react_1.useState)(0);
66
76
  const [stageIdx, setStageIdx] = (0, react_1.useState)(0);
77
+ const effectiveCrawlStep = crawlStep !== null && crawlStep !== void 0 ? crawlStep : CRAWL_STEP;
78
+ // Stable refs so effects don't restart when props change after mount
79
+ const crawlStepRef = (0, react_1.useRef)(effectiveCrawlStep);
80
+ const onCompleteRef = (0, react_1.useRef)(onComplete);
81
+ (0, react_1.useEffect)(() => { crawlStepRef.current = effectiveCrawlStep; }, [effectiveCrawlStep]);
82
+ (0, react_1.useEffect)(() => { onCompleteRef.current = onComplete; }, [onComplete]);
67
83
  const finishingRef = (0, react_1.useRef)(false);
68
84
  const completedRef = (0, react_1.useRef)(false);
69
85
  // Track current progress in a ref so the finish animation can read it synchronously
@@ -97,7 +113,7 @@ function TaxAxisProcessing({ onComplete, profile, reportReady = false, userConte
97
113
  progressRef.current = v;
98
114
  setProgress(v);
99
115
  };
100
- // ── Crawl: 0 → 80% — skipped entirely if reportReady already true on mount ──
116
+ // ── Crawl: 0 → 99% — runs once on mount, reads crawlStepRef so prop changes don't restart it ──
101
117
  (0, react_1.useEffect)(() => {
102
118
  if (reportReady || finishingRef.current)
103
119
  return;
@@ -106,14 +122,14 @@ function TaxAxisProcessing({ onComplete, profile, reportReady = false, userConte
106
122
  clearInterval(iv);
107
123
  return;
108
124
  }
109
- const next = Math.min(progressRef.current + CRAWL_STEP, CRAWL_CEILING);
125
+ const next = Math.min(progressRef.current + crawlStepRef.current, CRAWL_CEILING);
110
126
  setProgressSync(next);
111
127
  if (next >= CRAWL_CEILING)
112
128
  clearInterval(iv);
113
129
  }, CRAWL_TICK_MS);
114
130
  return () => clearInterval(iv);
115
- // reportReady intentionally in deps: if it arrives before mount effect runs, skip crawl
116
- }, [reportReady]);
131
+ // eslint-disable-next-line react-hooks/exhaustive-deps
132
+ }, []);
117
133
  // ── Stage labels: advance every STAGE_ADVANCE_MS ─────────────────
118
134
  (0, react_1.useEffect)(() => {
119
135
  const iv = setInterval(() => {
@@ -142,7 +158,7 @@ function TaxAxisProcessing({ onComplete, profile, reportReady = false, userConte
142
158
  setProgressSync(100);
143
159
  if (!completedRef.current) {
144
160
  completedRef.current = true;
145
- setTimeout(onComplete, 150);
161
+ setTimeout(() => onCompleteRef.current(), 150);
146
162
  }
147
163
  }
148
164
  };
@@ -25,7 +25,9 @@ function ProspectNextSteps({ sectionNum, bizName, onUpgrade, onPresent, onPrint,
25
25
  bizName,
26
26
  "'s documents to run the full TaxAxis analysis \u2014 computed savings with calculation traces, IRS citations, and a client-ready report."),
27
27
  react_1.default.createElement("div", { style: { display: "flex", gap: 10, flexWrap: "wrap" } },
28
- react_1.default.createElement(GlowBtn, { onClick: onUpgrade }, "Upload Documents"),
28
+ react_1.default.createElement(GlowBtn, { onClick: () => { if (onUpgrade) {
29
+ onUpgrade();
30
+ } } }, "Upload Documents"),
29
31
  react_1.default.createElement(GlowBtn, { onClick: onPresent, style: { fontSize: 13 } }, "Present to Client"),
30
32
  react_1.default.createElement(GlowBtn, { secondary: true, onClick: onPrint, style: { fontSize: 13 } }, "Download PDF")),
31
33
  react_1.default.createElement("div", { style: { fontSize: 11, color: theme_1.T.text4, marginTop: 12, fontFamily: theme_1.T.body } }, "Typical turnaround: same day. No obligation until you review the results.")))),
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare function ProspectReportSkeleton(): React.JSX.Element;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ProspectReportSkeleton = ProspectReportSkeleton;
7
+ const react_1 = __importDefault(require("react"));
8
+ const theme_1 = require("./theme");
9
+ // Dark-themed skeleton shown while generateProspectReport mutation is in-flight
10
+ function Bone({ w = "100%", h = 16, radius = 6, mb = 0 }) {
11
+ return (react_1.default.createElement("div", { style: {
12
+ width: w,
13
+ height: h,
14
+ borderRadius: radius,
15
+ background: `linear-gradient(90deg, ${theme_1.T.surface2} 25%, rgba(255,255,255,0.04) 50%, ${theme_1.T.surface2} 75%)`,
16
+ backgroundSize: "400% 100%",
17
+ animation: "skeletonShimmer 1.6s infinite linear",
18
+ marginBottom: mb,
19
+ flexShrink: 0,
20
+ } }));
21
+ }
22
+ function ProspectReportSkeleton() {
23
+ return (react_1.default.createElement(react_1.default.Fragment, null,
24
+ react_1.default.createElement("style", null, `
25
+ @keyframes skeletonShimmer {
26
+ 0% { background-position: 100% 0 }
27
+ 100% { background-position: -100% 0 }
28
+ }
29
+ `),
30
+ react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 16, padding: "36px 32px 28px", marginBottom: 24, boxShadow: theme_1.T.shadowLg, overflow: "hidden" } },
31
+ react_1.default.createElement("div", { style: { height: 4, background: `linear-gradient(90deg,${theme_1.T.accent},${theme_1.T.orange})`, marginBottom: 24, borderRadius: 2 } }),
32
+ react_1.default.createElement("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 20 } },
33
+ react_1.default.createElement(Bone, { w: 120, h: 12 }),
34
+ react_1.default.createElement(Bone, { w: 80, h: 12 })),
35
+ react_1.default.createElement(Bone, { w: "45%", h: 10, mb: 12 }),
36
+ react_1.default.createElement(Bone, { w: "70%", h: 36, mb: 12 }),
37
+ react_1.default.createElement(Bone, { w: "50%", h: 14, mb: 24 }),
38
+ react_1.default.createElement("div", { style: { height: 1, background: theme_1.T.border, marginBottom: 20 } }),
39
+ react_1.default.createElement(Bone, { w: "55%", h: 44, mb: 12 }),
40
+ react_1.default.createElement(Bone, { w: "30%", h: 12 })),
41
+ react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", marginBottom: 24, boxShadow: theme_1.T.shadow } },
42
+ react_1.default.createElement(Bone, { w: "40%", h: 10, mb: 16 }),
43
+ react_1.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 16 } }, [1, 2, 3].map(i => (react_1.default.createElement("div", { key: i },
44
+ react_1.default.createElement(Bone, { w: "60%", h: 28, mb: 8 }),
45
+ react_1.default.createElement(Bone, { w: "80%", h: 10 })))))),
46
+ react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", marginBottom: 24, boxShadow: theme_1.T.shadow } },
47
+ react_1.default.createElement(Bone, { w: "25%", h: 10, mb: 16 }),
48
+ react_1.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 16 } }, [1, 2, 3, 4].map(i => (react_1.default.createElement("div", { key: i },
49
+ react_1.default.createElement(Bone, { w: "70%", h: 22, mb: 8 }),
50
+ react_1.default.createElement(Bone, { w: "80%", h: 10 })))))),
51
+ react_1.default.createElement("div", { style: { marginBottom: 24 } },
52
+ react_1.default.createElement(Bone, { w: "30%", h: 10, mb: 12 }),
53
+ react_1.default.createElement(Bone, { w: "100%", h: 2, mb: 16 }),
54
+ react_1.default.createElement(Bone, { w: "100%", h: 14, mb: 8 }),
55
+ react_1.default.createElement(Bone, { w: "95%", h: 14, mb: 8 }),
56
+ react_1.default.createElement(Bone, { w: "80%", h: 14 })),
57
+ [1, 2, 3].map(i => (react_1.default.createElement("div", { key: i, style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", marginBottom: 14, boxShadow: theme_1.T.shadow } },
58
+ react_1.default.createElement("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 20 } },
59
+ react_1.default.createElement("div", { style: { display: "flex", gap: 14, alignItems: "center" } },
60
+ react_1.default.createElement("div", { style: { width: 34, height: 34, borderRadius: "50%", background: theme_1.T.surface2 } }),
61
+ react_1.default.createElement("div", null,
62
+ react_1.default.createElement(Bone, { w: 160, h: 16, mb: 8 }),
63
+ react_1.default.createElement(Bone, { w: 100, h: 10 }))),
64
+ react_1.default.createElement("div", { style: { textAlign: "right" } },
65
+ react_1.default.createElement(Bone, { w: 90, h: 22, mb: 6 }),
66
+ react_1.default.createElement(Bone, { w: 80, h: 10 }))),
67
+ react_1.default.createElement(Bone, { w: "100%", h: 12, mb: 6 }),
68
+ react_1.default.createElement(Bone, { w: "90%", h: 12, mb: 6 }),
69
+ react_1.default.createElement(Bone, { w: "75%", h: 12, mb: 16 }),
70
+ react_1.default.createElement(Bone, { w: "100%", h: 12, mb: 6 }),
71
+ react_1.default.createElement(Bone, { w: "85%", h: 12 })))),
72
+ react_1.default.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 10, padding: "20px 0", color: theme_1.T.text4, fontFamily: theme_1.T.body, fontSize: 12 } },
73
+ react_1.default.createElement("div", { style: { width: 6, height: 6, borderRadius: "50%", background: theme_1.T.accent, animation: "pulse 1s infinite" } }),
74
+ "Generating your prospect report\u2026"),
75
+ react_1.default.createElement("style", null, `
76
+ @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.3} }
77
+ `)));
78
+ }
@@ -1,10 +1,16 @@
1
1
  import React from "react";
2
2
  import type { Strategy, ClientProfile, ComputedMap } from "../../lib/types";
3
+ export interface LlmStrategyNarrative {
4
+ whyItMatters: string;
5
+ whatWeDo: string;
6
+ }
3
7
  interface ProspectStrategyCardProps {
4
8
  strategy: Strategy;
5
9
  index: number;
6
10
  profile: ClientProfile;
7
11
  computed: ComputedMap;
12
+ /** LLM-generated narrative — when present, overrides all static/computed text */
13
+ llmNarrative?: LlmStrategyNarrative;
8
14
  }
9
- export declare function ProspectStrategyCard({ strategy: s, index: i, profile, computed }: ProspectStrategyCardProps): React.JSX.Element;
15
+ export declare function ProspectStrategyCard({ strategy: s, index: i, profile, computed, llmNarrative }: ProspectStrategyCardProps): React.JSX.Element;
10
16
  export {};
@@ -8,17 +8,19 @@ const react_1 = __importDefault(require("react"));
8
8
  const strategyNarrative_1 = require("../../lib/data/strategyNarrative");
9
9
  const strategyProspect_1 = require("../../lib/data/strategyProspect");
10
10
  const theme_1 = require("./theme");
11
- function ProspectStrategyCard({ strategy: s, index: i, profile, computed }) {
12
- var _a, _b;
11
+ function ProspectStrategyCard({ strategy: s, index: i, profile, computed, llmNarrative }) {
12
+ var _a, _b, _c, _d;
13
13
  const p = strategyProspect_1.STRATEGY_PROSPECT[s.rank];
14
14
  const narr = strategyNarrative_1.STRATEGY_NARRATIVE[s.rank];
15
15
  const c = computed.get(s.rank);
16
- const whyText = narr ? narr.whyMatters(profile) : strategyNarrative_1.NARRATIVE_FALLBACK.whyMatters(s);
17
- const breakdownText = narr ? narr.breakdown(profile, c) : strategyNarrative_1.NARRATIVE_FALLBACK.breakdown(s, c);
18
- const specialistText = (narr === null || narr === void 0 ? void 0 : narr.whySpecialist) || strategyNarrative_1.NARRATIVE_FALLBACK.whySpecialist(s);
19
- const nextText = narr ? narr.nextSteps : strategyNarrative_1.NARRATIVE_FALLBACK.nextSteps(s);
20
16
  const scaledLo = Math.round(((_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo) / 1000);
21
17
  const scaledHi = Math.round(((_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi) / 1000);
18
+ // When LLM narrative is provided, use it for whyItMatters and whatWeDo.
19
+ // HOW SAVINGS BREAK DOWN and WHY YOU NEED A SPECIALIST remain deterministic.
20
+ const whyText = (_c = llmNarrative === null || llmNarrative === void 0 ? void 0 : llmNarrative.whyItMatters) !== null && _c !== void 0 ? _c : (narr ? narr.whyMatters(profile) : strategyNarrative_1.NARRATIVE_FALLBACK.whyMatters(s));
21
+ const breakdownText = narr ? narr.breakdown(profile, c) : strategyNarrative_1.NARRATIVE_FALLBACK.breakdown(s, c);
22
+ const specialistText = (narr === null || narr === void 0 ? void 0 : narr.whySpecialist) || strategyNarrative_1.NARRATIVE_FALLBACK.whySpecialist(s);
23
+ const nextText = (_d = llmNarrative === null || llmNarrative === void 0 ? void 0 : llmNarrative.whatWeDo) !== null && _d !== void 0 ? _d : (narr ? narr.nextSteps : strategyNarrative_1.NARRATIVE_FALLBACK.nextSteps(s));
22
24
  return (react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", boxShadow: theme_1.T.shadow } },
23
25
  react_1.default.createElement("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 16 } },
24
26
  react_1.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14 } },
@@ -1,9 +1,41 @@
1
1
  import React from "react";
2
2
  import type { ClientProfile, TaxAxisScreenProps } from "../../lib/types";
3
+ import type { LlmStrategyNarrative } from "./ProspectStrategyCard";
4
+ /** LLM output payload shape returned by generateProspectReport mutation */
5
+ export interface LlmOutputPayload {
6
+ execSummary: string;
7
+ strategyNarratives: Record<string, LlmStrategyNarrative>;
8
+ additionalStrategiesBlurb: string;
9
+ }
10
+ /** Backend-computed math payload */
11
+ export interface BackendComputePayload {
12
+ savingsLo: number;
13
+ savingsHi: number;
14
+ eligibleCount: number;
15
+ highPriorityCount: number;
16
+ quickWinCount: number;
17
+ gapBlock: {
18
+ currentTax: number;
19
+ optimizedTax: number;
20
+ realizedSavings: number;
21
+ realizedPctOfCurrent: number;
22
+ fedRatePct: number;
23
+ stateRatePct: number;
24
+ };
25
+ barData: Array<{
26
+ name: string;
27
+ mid: number;
28
+ pct: number;
29
+ }>;
30
+ }
3
31
  export interface TaxAxisProspectReportProps extends TaxAxisScreenProps {
4
32
  profile: ClientProfile;
33
+ /** When provided (from generateProspectReport mutation), all math comes from backend */
34
+ backendComputePayload?: BackendComputePayload | null;
35
+ /** LLM-generated narrative text from the mutation */
36
+ outputPayload?: LlmOutputPayload | null;
5
37
  onUpgrade?: () => void;
6
38
  onPresent?: () => void;
7
39
  onReset?: () => void;
8
40
  }
9
- export declare function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }: TaxAxisProspectReportProps): React.JSX.Element;
41
+ export declare function TaxAxisProspectReport({ profile, backendComputePayload, outputPayload, onUpgrade, onPresent, onReset, }: TaxAxisProspectReportProps): React.JSX.Element;
@@ -35,47 +35,48 @@ const SampleAnalysisPreview_1 = require("./SampleAnalysisPreview");
35
35
  const ProspectDocuments_1 = require("./ProspectDocuments");
36
36
  const ProspectNextSteps_1 = require("./ProspectNextSteps");
37
37
  const ProspectPrintView_1 = require("./ProspectPrintView");
38
- function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
38
+ function TaxAxisProspectReport({ profile, backendComputePayload, outputPayload, onUpgrade, onPresent, onReset, }) {
39
+ var _a, _b, _c, _d, _e, _f, _g;
39
40
  const bizName = profile.bizName || "Client";
40
41
  const [confirmReset, setConfirmReset] = (0, react_1.useState)(false);
41
42
  const [printMode, setPrintMode] = (0, react_1.useState)(false);
42
- // ═══ ELIGIBLE STRATEGIES ═══
43
+ // ═══ ELIGIBLE STRATEGIES (client-side, for rendering strategy cards) ═══
43
44
  const eligible = (0, react_1.useMemo)(() => (0, compute_1.filterEligibleStrategies)(profile), [profile]);
44
45
  const high = eligible.filter(s => s.priority === "HIGH");
45
46
  const quick = eligible.filter(s => s.priority === "QUICK WIN");
46
- // Top 3 by score for the sales narrative
47
47
  const top3 = (0, react_1.useMemo)(() => [...eligible].sort((a, b) => b.score - a.score).slice(0, 3), [eligible]);
48
48
  const remaining = eligible.length - top3.length;
49
49
  const remainingNames = eligible.filter(s => !top3.includes(s)).slice(0, 4).map(s => s.name);
50
- // ═══ SAVINGS RANGE (dynamic computation) ═══
50
+ // ═══ SAVINGS RANGE backend-authoritative when available ═══
51
51
  const computed = (0, react_1.useMemo)(() => (0, compute_1.computeAllStrategies)(profile), [profile]);
52
- const displayLo = Math.round(eligible.reduce((a, s) => { var _a, _b; return a + ((_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.lo) !== null && _b !== void 0 ? _b : s.lo); }, 0) / 1000);
53
- const displayHi = Math.round(eligible.reduce((a, s) => { var _a, _b; return a + ((_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.hi) !== null && _b !== void 0 ? _b : s.hi); }, 0) / 1000);
54
- // Savings bar chart data top 3 strategies with midpoint + percentage
55
- const totalMid = eligible.reduce((a, s) => { var _a, _b; const c = computed.get(s.rank); return a + (((_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo) + ((_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi)) / 2; }, 0);
56
- const barData = top3.map(s => { var _a, _b; const c = computed.get(s.rank); const mid = Math.round((((_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo) + ((_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi)) / 2); return { name: s.name, mid, pct: totalMid > 0 ? Math.round(mid / totalMid * 100) : 0 }; });
52
+ const displayLo = backendComputePayload
53
+ ? Math.round(backendComputePayload.savingsLo / 1000)
54
+ : Math.round(eligible.reduce((a, s) => { var _a, _b; return a + ((_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.lo) !== null && _b !== void 0 ? _b : s.lo); }, 0) / 1000);
55
+ const displayHi = backendComputePayload
56
+ ? Math.round(backendComputePayload.savingsHi / 1000)
57
+ : Math.round(eligible.reduce((a, s) => { var _a, _b; return a + ((_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.hi) !== null && _b !== void 0 ? _b : s.hi); }, 0) / 1000);
58
+ // ═══ GAP BLOCK — backend-authoritative when available ═══
59
+ const fedRatePct = (_a = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.fedRatePct) !== null && _a !== void 0 ? _a : (parseFloat((profile.federalRate || "24").replace("%", "")) || 24);
60
+ const stateRatePct = (_b = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.stateRatePct) !== null && _b !== void 0 ? _b : (parseFloat(profile.stateRate || "4.95") || 4.95);
61
+ const currentTax = (_c = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.currentTax) !== null && _c !== void 0 ? _c : Math.round(((0, compute_1.parseNum)(profile.netIncome) || Math.round((0, compute_1.parseNum)(profile.revenue) * 0.20))
62
+ * (fedRatePct + stateRatePct) / 100);
63
+ const realizedSavings = (_d = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.realizedSavings) !== null && _d !== void 0 ? _d : Math.min(Math.round((displayLo + displayHi) / 2 * 1000 * 0.325), Math.round(currentTax * 0.6));
64
+ const optimizedTax = (_e = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.optimizedTax) !== null && _e !== void 0 ? _e : (currentTax - realizedSavings);
65
+ const realizedPctOfCurrent = (_f = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.realizedPctOfCurrent) !== null && _f !== void 0 ? _f : (currentTax > 0 ? Math.round((realizedSavings / currentTax) * 100) : 0);
66
+ // ═══ BAR CHART DATA ═══
67
+ const barData = (_g = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.barData) !== null && _g !== void 0 ? _g : (() => {
68
+ const totalMid = eligible.reduce((a, s) => { var _a, _b; const c = computed.get(s.rank); return a + (((_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo) + ((_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi)) / 2; }, 0);
69
+ return top3.map(s => { var _a, _b; const c = computed.get(s.rank); const mid = Math.round((((_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo) + ((_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi)) / 2); return { name: s.name, mid, pct: totalMid > 0 ? Math.round(mid / totalMid * 100) : 0 }; });
70
+ })();
57
71
  const maxMid = Math.max(...barData.map(b => b.mid), 1);
58
- // ═══ GAP BLOCK MATH — Current vs Optimized tax position ═══
59
- const rev = (0, compute_1.parseNum)(profile.revenue);
60
- const netIncome = (0, compute_1.parseNum)(profile.netIncome) || Math.round(rev * 0.20);
61
- const fedRatePct = parseFloat((profile.federalRate || "24").replace("%", "")) || 24;
62
- const stateRatePct = parseFloat(profile.stateRate || "4.95") || 4.95;
63
- const combinedRate = (fedRatePct + stateRatePct) / 100;
64
- const currentTax = Math.round(netIncome * combinedRate);
65
- const theoreticalMidK = (displayLo + displayHi) / 2;
66
- const realizedSavings = Math.min(Math.round(theoreticalMidK * 1000 * 0.325), Math.round(currentTax * 0.6));
67
- const optimizedTax = currentTax - realizedSavings;
68
- const realizedPctOfCurrent = currentTax > 0 ? Math.round((realizedSavings / currentTax) * 100) : 0;
69
- // Section numbering — shifts when Additional Strategies section is present
70
- const sectionNums = remaining > 0
71
- ? { recommended: "02", additional: "03", sample: "04", documents: "05", nextSteps: "06" }
72
- : { recommended: "02", additional: null, sample: "03", documents: "04", nextSteps: "05" };
73
72
  // ═══ DOCUMENT GROUPING ═══
74
73
  const allDocsNeeded = [...new Set(eligible.flatMap(s => { var _a; return ((_a = strategyProspect_1.STRATEGY_PROSPECT[s.rank]) === null || _a === void 0 ? void 0 : _a.docsNeeded) || []; }))];
75
74
  const requiredDocs = ["Profit & Loss Statement (current year)", "Balance Sheet (current year)"];
76
75
  const recommendedDocs = ["Federal Tax Return (most recent)", "W-2 / Officer Compensation records"];
77
76
  const conditionalDocs = allDocsNeeded.filter(d => !requiredDocs.includes(d) && !recommendedDocs.includes(d)).slice(0, 5);
78
- // ═══ PRINT HANDLER ═══
77
+ const sectionNums = remaining > 0
78
+ ? { recommended: "02", additional: "03", sample: "04", documents: "05", nextSteps: "06" }
79
+ : { recommended: "02", additional: null, sample: "03", documents: "04", nextSteps: "05" };
79
80
  const handlePrint = () => {
80
81
  setPrintMode(true);
81
82
  requestAnimationFrame(() => {
@@ -86,18 +87,16 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
86
87
  });
87
88
  });
88
89
  };
89
- // ═══ PRINT MODE — Light theme layout ═══
90
90
  if (printMode) {
91
91
  return (react_1.default.createElement(ProspectPrintView_1.ProspectPrintView, { profile: profile, bizName: bizName, displayLo: displayLo, displayHi: displayHi, currentTax: currentTax, optimizedTax: optimizedTax, realizedSavings: realizedSavings, realizedPctOfCurrent: realizedPctOfCurrent, fedRatePct: fedRatePct, stateRatePct: stateRatePct, eligible: eligible, high: high, quick: quick, top3: top3, computed: computed, fmtTax: compute_1.fmtTax, fmtSavings: compute_1.fmtSavings, requiredDocs: requiredDocs, recommendedDocs: recommendedDocs, conditionalDocs: conditionalDocs }));
92
92
  }
93
- // ═══ INTERACTIVE MODE — Dark theme layout ═══
94
93
  return (react_1.default.createElement("div", null,
95
94
  react_1.default.createElement(ProspectCover_1.ProspectCover, { profile: profile, bizName: bizName, displayLo: displayLo, displayHi: displayHi }),
96
95
  react_1.default.createElement(TaxPositionGap_1.TaxPositionGap, { bizName: bizName, currentTax: currentTax, optimizedTax: optimizedTax, realizedSavings: realizedSavings, realizedPctOfCurrent: realizedPctOfCurrent, fedRatePct: fedRatePct, stateRatePct: stateRatePct, eligibleCount: eligible.length, fmtTax: compute_1.fmtTax, fmtSavings: compute_1.fmtSavings }),
97
96
  react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", marginBottom: 24, boxShadow: theme_1.T.shadow } },
98
97
  react_1.default.createElement("div", { style: { fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.15em", color: theme_1.T.text4, marginBottom: 16, fontFamily: theme_1.T.mono } }, "AT A GLANCE"),
99
98
  react_1.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 16 } }, [
100
- { v: `$${displayLo}K\u2013$${displayHi}K`, l: "Est. Savings" },
99
+ { v: `$${displayLo}K–$${displayHi}K`, l: "Est. Savings" },
101
100
  { v: String(eligible.length), l: "Strategies Found" },
102
101
  { v: String(high.length), l: "High Impact" },
103
102
  { v: String(quick.length), l: "Quick Wins" },
@@ -109,29 +108,30 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
109
108
  react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: theme_1.T.accent, fontFamily: theme_1.T.mono } }, "01"),
110
109
  react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.12em", color: theme_1.T.text4, fontFamily: theme_1.T.body } }, "EXECUTIVE SUMMARY")),
111
110
  react_1.default.createElement("div", { style: { height: 2, background: theme_1.T.accent, width: 48, marginBottom: 16 } }),
112
- react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8, marginBottom: 12 } },
113
- "We evaluated ",
114
- bizName,
115
- "'s tax position against 25 federal strategies and identified ",
116
- react_1.default.createElement("strong", { style: { color: theme_1.T.white } },
117
- eligible.length,
118
- " applicable opportunities"),
119
- " with combined estimated savings of ",
120
- react_1.default.createElement("strong", { style: { color: theme_1.T.accentLt } },
121
- "$",
122
- displayLo,
123
- "K\u2013$",
124
- displayHi,
125
- "K annually"),
126
- ". ",
127
- high.length,
128
- " strategies could have significant impact."),
129
- react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8 } },
130
- "The top ",
131
- top3.length,
132
- " strategies are detailed below.",
133
- quick.length > 0 ? ` ${quick.length} can be implemented this week with no structural changes.` : "",
134
- " These are profile-based estimates \u2014 uploading financial documents will refine savings calculations and unlock all 25 strategies.")),
111
+ (outputPayload === null || outputPayload === void 0 ? void 0 : outputPayload.execSummary) ? (react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8, marginBottom: 12 } }, outputPayload.execSummary)) : (react_1.default.createElement(react_1.default.Fragment, null,
112
+ react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8, marginBottom: 12 } },
113
+ "We evaluated ",
114
+ bizName,
115
+ "'s tax position against 25 federal strategies and identified ",
116
+ react_1.default.createElement("strong", { style: { color: theme_1.T.white } },
117
+ eligible.length,
118
+ " applicable opportunities"),
119
+ " with combined estimated savings of ",
120
+ react_1.default.createElement("strong", { style: { color: theme_1.T.accentLt } },
121
+ "$",
122
+ displayLo,
123
+ "K\u2013$",
124
+ displayHi,
125
+ "K annually"),
126
+ ". ",
127
+ high.length,
128
+ " strategies could have significant impact."),
129
+ react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8 } },
130
+ "The top ",
131
+ top3.length,
132
+ " strategies are detailed below.",
133
+ quick.length > 0 ? ` ${quick.length} can be implemented this week with no structural changes.` : "",
134
+ " These are profile-based estimates \u2014 uploading financial documents will refine savings calculations and unlock all 25 strategies.")))),
135
135
  react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "20px 24px", marginBottom: 24, boxShadow: theme_1.T.shadow } },
136
136
  react_1.default.createElement("div", { style: { fontSize: 10, fontWeight: 700, color: theme_1.T.text4, textTransform: "uppercase", letterSpacing: "0.12em", marginBottom: 12, fontFamily: theme_1.T.mono } }, "SAVINGS DISTRIBUTION"),
137
137
  barData.map((b, i) => (react_1.default.createElement("div", { key: i, style: { display: "flex", alignItems: "center", gap: 10, marginBottom: i < barData.length - 1 ? 8 : 0 } },
@@ -151,7 +151,10 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
151
151
  react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: theme_1.T.accent, fontFamily: theme_1.T.mono } }, "02"),
152
152
  react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.12em", color: theme_1.T.text4, fontFamily: theme_1.T.body } }, "RECOMMENDED STRATEGIES")),
153
153
  react_1.default.createElement("div", { style: { height: 2, background: theme_1.T.accent, width: 48, marginBottom: 16 } }),
154
- react_1.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 14 } }, top3.map((s, i) => (react_1.default.createElement(ProspectStrategyCard_1.ProspectStrategyCard, { key: s.rank, strategy: s, index: i, profile: profile, computed: computed }))))),
154
+ react_1.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 14 } }, top3.map((s, i) => {
155
+ var _a;
156
+ return (react_1.default.createElement(ProspectStrategyCard_1.ProspectStrategyCard, { key: s.rank, strategy: s, index: i, profile: profile, computed: computed, llmNarrative: (_a = outputPayload === null || outputPayload === void 0 ? void 0 : outputPayload.strategyNarratives) === null || _a === void 0 ? void 0 : _a[`S${s.rank}`] }));
157
+ }))),
155
158
  remaining > 0 && (react_1.default.createElement("div", { style: { marginBottom: 24 } },
156
159
  react_1.default.createElement("div", { style: { display: "flex", alignItems: "baseline", gap: 10, marginBottom: 6 } },
157
160
  react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: theme_1.T.accent, fontFamily: theme_1.T.mono } }, "03"),
@@ -162,10 +165,8 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
162
165
  "+ ",
163
166
  remaining,
164
167
  " more strategies identified"),
165
- react_1.default.createElement("div", { style: { fontSize: 13, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.7, marginBottom: 10 } },
166
- "Including: ",
167
- remainingNames.join(", "),
168
- remaining > 4 ? " and more" : ""),
168
+ react_1.default.createElement("div", { style: { fontSize: 13, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.7, marginBottom: 10 } }, (outputPayload === null || outputPayload === void 0 ? void 0 : outputPayload.additionalStrategiesBlurb)
169
+ || `Including: ${remainingNames.join(", ")}${remaining > 4 ? " and more" : ""}`),
169
170
  react_1.default.createElement("div", { style: { fontSize: 12, color: theme_1.T.text3, fontFamily: theme_1.T.body, lineHeight: 1.6, borderLeft: `3px solid ${theme_1.T.accent}`, paddingLeft: 12 } }, "Full analysis with documents unlocks specific savings estimates for every eligible strategy.")))),
170
171
  react_1.default.createElement(SampleAnalysisPreview_1.SampleAnalysisPreview, { sectionNum: sectionNums.sample, eligibleCount: eligible.length }),
171
172
  react_1.default.createElement(ProspectDocuments_1.ProspectDocuments, { sectionNum: sectionNums.documents, requiredDocs: requiredDocs, recommendedDocs: recommendedDocs, conditionalDocs: conditionalDocs }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paro.io/expert-shared-components",
3
- "version": "1.14.68",
3
+ "version": "1.14.70",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {