@paro.io/expert-shared-components 1.14.56 → 1.14.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/TaxAxis/TaxAxisApi.d.ts +1 -0
- package/lib/components/TaxAxis/TaxAxisShell.d.ts +1 -1
- package/lib/components/TaxAxis/TaxAxisShell.js +52 -2
- package/lib/components/TaxAxis/types.d.ts +5 -0
- package/lib/tax-axis/components/clientReport/Methodology.js +2 -2
- package/lib/tax-axis/components/dashboard/DashboardActions.js +4 -4
- package/lib/tax-axis/components/dashboard/DashboardSummary.js +5 -6
- package/lib/tax-axis/components/dashboard/StrategyDetailPanel.js +2 -4
- package/lib/tax-axis/components/dashboard/TaxAxisDashboard.js +162 -57
- package/lib/tax-axis/components/documents/DocumentCard.d.ts +1 -0
- package/lib/tax-axis/components/documents/DocumentCard.js +19 -3
- package/lib/tax-axis/components/documents/TaxAxisDocuments.d.ts +12 -1
- package/lib/tax-axis/components/documents/TaxAxisDocuments.js +110 -48
- package/lib/tax-axis/components/documents/qbo/QboAvailableReportsModal.d.ts +13 -0
- package/lib/tax-axis/components/documents/qbo/QboAvailableReportsModal.js +180 -0
- package/lib/tax-axis/components/documents/qbo/QboClientSelectorModal.d.ts +10 -0
- package/lib/tax-axis/components/documents/qbo/QboClientSelectorModal.js +155 -0
- package/lib/tax-axis/components/documents/qbo/QboConnectBanner.d.ts +9 -0
- package/lib/tax-axis/components/documents/qbo/QboConnectBanner.js +55 -0
- package/lib/tax-axis/components/documents/qbo/QboDocumentMappingModal.d.ts +10 -0
- package/lib/tax-axis/components/documents/qbo/QboDocumentMappingModal.js +202 -0
- package/lib/tax-axis/components/documents/qbo/QboImportingModal.d.ts +8 -0
- package/lib/tax-axis/components/documents/qbo/QboImportingModal.js +75 -0
- package/lib/tax-axis/components/documents/qbo/QboPermissionsModal.d.ts +8 -0
- package/lib/tax-axis/components/documents/qbo/QboPermissionsModal.js +126 -0
- package/lib/tax-axis/components/documents/qbo/index.d.ts +8 -0
- package/lib/tax-axis/components/documents/qbo/index.js +17 -0
- package/lib/tax-axis/components/documents/qbo/qboConstants.d.ts +24 -0
- package/lib/tax-axis/components/documents/qbo/qboConstants.js +71 -0
- package/lib/tax-axis/components/documents/qbo/types.d.ts +43 -0
- package/lib/tax-axis/components/documents/qbo/types.js +3 -0
- package/lib/tax-axis/components/documents/qbo/useQboFlow.d.ts +19 -0
- package/lib/tax-axis/components/documents/qbo/useQboFlow.js +207 -0
- package/lib/tax-axis/components/intake/ClientParametersSection.js +1 -1
- package/lib/tax-axis/components/intake/CpaIntakeQuestionsSection.js +3 -3
- package/lib/tax-axis/components/intake/IntakeCtaCards.js +1 -1
- package/lib/tax-axis/components/intake/RefineAnalysisSection.js +7 -7
- package/lib/tax-axis/components/intake/TaxAxisIntake.js +2 -2
- package/lib/tax-axis/components/processing/TaxAxisProcessing.js +30 -5
- package/package.json +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface QboCompany {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
initials: string;
|
|
5
|
+
color: string;
|
|
6
|
+
}
|
|
7
|
+
export interface QboReportOption {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
documentType: string;
|
|
11
|
+
available: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface QboMappingRow {
|
|
14
|
+
qboField: string;
|
|
15
|
+
taxAxisField: string;
|
|
16
|
+
status: "mapped" | "unmapped" | "review";
|
|
17
|
+
}
|
|
18
|
+
export type QboImportStep = "connecting" | "fetching_reports" | "importing_pl" | "importing_bs";
|
|
19
|
+
export type QboModalStep = "closed" | "permissions" | "oauth_pending" | "client_selector" | "available_reports" | "mapping" | "importing" | "done";
|
|
20
|
+
export interface QboFlowState {
|
|
21
|
+
modalStep: QboModalStep;
|
|
22
|
+
selectedCompany: QboCompany | null;
|
|
23
|
+
selectedReports: string[];
|
|
24
|
+
importStepIndex: number;
|
|
25
|
+
error: string | null;
|
|
26
|
+
}
|
|
27
|
+
export interface QboFlowProps {
|
|
28
|
+
qboConnected?: boolean;
|
|
29
|
+
qboCompanyName?: string | null;
|
|
30
|
+
qboAuthorizeUrl?: string;
|
|
31
|
+
qboGetClientsUrl?: string;
|
|
32
|
+
/** Selected tax year from the session profile — pre-fills the year dropdown */
|
|
33
|
+
profileYear?: number;
|
|
34
|
+
onImportQboReport?: (sessionId: string, realmId: string, reportType: string, accountingMethod: string, year?: number) => Promise<any>;
|
|
35
|
+
fetchUploadedDocuments?: () => Promise<any[]>;
|
|
36
|
+
sessionId?: string;
|
|
37
|
+
onQboConnected?: (companyName: string) => void;
|
|
38
|
+
onQboDisconnected?: () => void;
|
|
39
|
+
onQboImportComplete?: (importedReports: {
|
|
40
|
+
slot: string;
|
|
41
|
+
fileName: string;
|
|
42
|
+
}[]) => void;
|
|
43
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { QboCompany, QboModalStep, QboFlowProps } from "./types";
|
|
2
|
+
export declare function useQboFlow({ qboConnected, qboCompanyName, qboAuthorizeUrl, onImportQboReport, fetchUploadedDocuments, sessionId, profileYear, onQboConnected, onQboDisconnected, onQboImportComplete, }: QboFlowProps): {
|
|
3
|
+
modalStep: QboModalStep;
|
|
4
|
+
selectedCompany: QboCompany | null;
|
|
5
|
+
selectedReports: string[];
|
|
6
|
+
selectedYear: number;
|
|
7
|
+
setSelectedYear: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
8
|
+
importStepIndex: number;
|
|
9
|
+
error: string | null;
|
|
10
|
+
companies: QboCompany[];
|
|
11
|
+
openPermissions: () => void;
|
|
12
|
+
openImport: () => void;
|
|
13
|
+
handlePermissionsContinue: () => void;
|
|
14
|
+
handleCompanySelected: (company: QboCompany) => void;
|
|
15
|
+
handleReportsConfirmed: (reportIds: string[]) => Promise<void>;
|
|
16
|
+
handleMappingConfirm: () => Promise<void>;
|
|
17
|
+
handleDisconnect: () => void;
|
|
18
|
+
closeModal: () => void;
|
|
19
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.useQboFlow = useQboFlow;
|
|
13
|
+
const react_1 = require("react");
|
|
14
|
+
const qboConstants_1 = require("./qboConstants");
|
|
15
|
+
function buildCompany(companyName) {
|
|
16
|
+
const name = companyName || "My QuickBooks Company";
|
|
17
|
+
const initials = name
|
|
18
|
+
.split(/\s+/)
|
|
19
|
+
.slice(0, 2)
|
|
20
|
+
.map((w) => { var _a, _b; return (_b = (_a = w[0]) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : ""; })
|
|
21
|
+
.join("");
|
|
22
|
+
return { id: "connected", name, initials, color: "#2CA01C" };
|
|
23
|
+
}
|
|
24
|
+
function useQboFlow({ qboConnected, qboCompanyName, qboAuthorizeUrl, onImportQboReport, fetchUploadedDocuments, sessionId, profileYear, onQboConnected, onQboDisconnected, onQboImportComplete, }) {
|
|
25
|
+
const [modalStep, setModalStep] = (0, react_1.useState)("closed");
|
|
26
|
+
const [selectedCompany, setSelectedCompany] = (0, react_1.useState)(qboConnected ? buildCompany(qboCompanyName) : null);
|
|
27
|
+
const [selectedReports, setSelectedReports] = (0, react_1.useState)([]);
|
|
28
|
+
const [selectedYear, setSelectedYear] = (0, react_1.useState)(profileYear !== null && profileYear !== void 0 ? profileYear : new Date().getFullYear());
|
|
29
|
+
const [importStepIndex, setImportStepIndex] = (0, react_1.useState)(0);
|
|
30
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
31
|
+
const pollTimerRef = (0, react_1.useRef)(null);
|
|
32
|
+
// Keep selectedYear in sync if profileYear prop changes
|
|
33
|
+
const prevProfileYear = (0, react_1.useRef)(profileYear);
|
|
34
|
+
if (profileYear !== prevProfileYear.current) {
|
|
35
|
+
prevProfileYear.current = profileYear;
|
|
36
|
+
if (profileYear)
|
|
37
|
+
setSelectedYear(profileYear);
|
|
38
|
+
}
|
|
39
|
+
// Sync company when qboCompanyName prop changes
|
|
40
|
+
(0, react_1.useEffect)(() => {
|
|
41
|
+
if (qboConnected)
|
|
42
|
+
setSelectedCompany(buildCompany(qboCompanyName));
|
|
43
|
+
}, [qboConnected, qboCompanyName]);
|
|
44
|
+
// Cleanup poll timer on unmount
|
|
45
|
+
(0, react_1.useEffect)(() => {
|
|
46
|
+
return () => {
|
|
47
|
+
if (pollTimerRef.current)
|
|
48
|
+
clearInterval(pollTimerRef.current);
|
|
49
|
+
};
|
|
50
|
+
}, []);
|
|
51
|
+
// ── Already connected: skip straight to report selection ──
|
|
52
|
+
const openImport = (0, react_1.useCallback)(() => {
|
|
53
|
+
setError(null);
|
|
54
|
+
setSelectedCompany(buildCompany(qboCompanyName));
|
|
55
|
+
setModalStep("available_reports");
|
|
56
|
+
}, [qboCompanyName]);
|
|
57
|
+
// ── Not yet connected: start OAuth flow ──
|
|
58
|
+
const openPermissions = (0, react_1.useCallback)(() => {
|
|
59
|
+
setError(null);
|
|
60
|
+
setModalStep("permissions");
|
|
61
|
+
}, []);
|
|
62
|
+
// ── After permissions: start OAuth popup ──
|
|
63
|
+
const handlePermissionsContinue = (0, react_1.useCallback)(() => {
|
|
64
|
+
const isDemoMode = typeof window !== "undefined" &&
|
|
65
|
+
(!qboAuthorizeUrl || window.location.hostname === "localhost");
|
|
66
|
+
if (isDemoMode) {
|
|
67
|
+
setSelectedCompany(buildCompany(qboCompanyName));
|
|
68
|
+
setModalStep("client_selector");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
setModalStep("oauth_pending");
|
|
72
|
+
const authorizeUrl = qboAuthorizeUrl;
|
|
73
|
+
const redirectUri = `${window.location.origin}/integrations-hub/callback`;
|
|
74
|
+
const authUrl = authorizeUrl.includes("?")
|
|
75
|
+
? `${authorizeUrl}&redirectUri=${encodeURIComponent(redirectUri)}`
|
|
76
|
+
: `${authorizeUrl}?redirectUri=${encodeURIComponent(redirectUri)}`;
|
|
77
|
+
const popup = window.open(authUrl, "qbo_oauth", "width=600,height=700,scrollbars=yes");
|
|
78
|
+
// Poll for popup close — parent will re-fetch EPS and update qboConnected prop
|
|
79
|
+
let elapsed = 0;
|
|
80
|
+
pollTimerRef.current = setInterval(() => {
|
|
81
|
+
elapsed += 500;
|
|
82
|
+
if (popup && popup.closed) {
|
|
83
|
+
if (pollTimerRef.current)
|
|
84
|
+
clearInterval(pollTimerRef.current);
|
|
85
|
+
// Parent prop update will trigger the useEffect above to set selectedCompany
|
|
86
|
+
setModalStep("client_selector");
|
|
87
|
+
}
|
|
88
|
+
if (elapsed >= 120000) {
|
|
89
|
+
if (pollTimerRef.current)
|
|
90
|
+
clearInterval(pollTimerRef.current);
|
|
91
|
+
setError("QuickBooks authorization timed out. Please try again.");
|
|
92
|
+
setModalStep("closed");
|
|
93
|
+
}
|
|
94
|
+
}, 500);
|
|
95
|
+
}, [qboAuthorizeUrl, qboCompanyName]);
|
|
96
|
+
// ── Company selected ──
|
|
97
|
+
const handleCompanySelected = (0, react_1.useCallback)((company) => {
|
|
98
|
+
setSelectedCompany(company);
|
|
99
|
+
setModalStep("available_reports");
|
|
100
|
+
}, []);
|
|
101
|
+
// ── Reports confirmed → import ──
|
|
102
|
+
const handleReportsConfirmed = (0, react_1.useCallback)((reportIds) => __awaiter(this, void 0, void 0, function* () {
|
|
103
|
+
var _a;
|
|
104
|
+
setSelectedReports(reportIds);
|
|
105
|
+
if (!onImportQboReport || !sessionId || !selectedCompany) {
|
|
106
|
+
setError("Import configuration is incomplete.");
|
|
107
|
+
setModalStep("closed");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
setModalStep("importing");
|
|
111
|
+
setImportStepIndex(0);
|
|
112
|
+
try {
|
|
113
|
+
yield new Promise((r) => setTimeout(r, 600));
|
|
114
|
+
setImportStepIndex(1);
|
|
115
|
+
yield new Promise((r) => setTimeout(r, 400));
|
|
116
|
+
setImportStepIndex(2);
|
|
117
|
+
const realmId = selectedCompany.id;
|
|
118
|
+
const importPromises = reportIds.map((reportId) => {
|
|
119
|
+
const reportType = reportId === "profit_loss" ? "profit_loss" : "balance_sheet";
|
|
120
|
+
return onImportQboReport(sessionId, realmId, reportType, "Accrual", selectedYear);
|
|
121
|
+
});
|
|
122
|
+
const results = yield Promise.allSettled(importPromises);
|
|
123
|
+
setImportStepIndex(3);
|
|
124
|
+
const failures = results.filter((r) => r.status === "rejected");
|
|
125
|
+
if (failures.length === results.length) {
|
|
126
|
+
const msg = ((_a = failures[0].reason) === null || _a === void 0 ? void 0 : _a.message) || "Import failed";
|
|
127
|
+
setError(msg);
|
|
128
|
+
setModalStep("closed");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
yield new Promise((r) => setTimeout(r, 500));
|
|
132
|
+
setImportStepIndex(4);
|
|
133
|
+
if (failures.length > 0) {
|
|
134
|
+
const msg = failures
|
|
135
|
+
.map((f) => { var _a; return ((_a = f.reason) === null || _a === void 0 ? void 0 : _a.message) || "Import failed"; })
|
|
136
|
+
.join("; ");
|
|
137
|
+
setError(msg);
|
|
138
|
+
}
|
|
139
|
+
if (fetchUploadedDocuments) {
|
|
140
|
+
try {
|
|
141
|
+
yield fetchUploadedDocuments();
|
|
142
|
+
}
|
|
143
|
+
catch ( /* non-fatal */_b) { /* non-fatal */ }
|
|
144
|
+
}
|
|
145
|
+
if (onQboConnected)
|
|
146
|
+
onQboConnected(selectedCompany.name);
|
|
147
|
+
if (onQboImportComplete) {
|
|
148
|
+
const mappedDocs = reportIds
|
|
149
|
+
.map((reportId) => {
|
|
150
|
+
const option = qboConstants_1.QBO_REPORT_OPTIONS.find((o) => o.id === reportId);
|
|
151
|
+
return option ? { slot: option.documentType, fileName: option.label } : null;
|
|
152
|
+
})
|
|
153
|
+
.filter((m) => m !== null);
|
|
154
|
+
onQboImportComplete(mappedDocs);
|
|
155
|
+
}
|
|
156
|
+
setModalStep("done");
|
|
157
|
+
setTimeout(() => { setModalStep("closed"); }, 1500);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
const msg = err instanceof Error ? err.message : "Import failed. Please try again.";
|
|
161
|
+
setError(msg);
|
|
162
|
+
setModalStep("closed");
|
|
163
|
+
}
|
|
164
|
+
}), [
|
|
165
|
+
onImportQboReport,
|
|
166
|
+
sessionId,
|
|
167
|
+
selectedCompany,
|
|
168
|
+
selectedYear,
|
|
169
|
+
fetchUploadedDocuments,
|
|
170
|
+
onQboConnected,
|
|
171
|
+
onQboImportComplete,
|
|
172
|
+
]);
|
|
173
|
+
const handleMappingConfirm = (0, react_1.useCallback)(() => __awaiter(this, void 0, void 0, function* () {
|
|
174
|
+
yield handleReportsConfirmed(selectedReports);
|
|
175
|
+
}), [handleReportsConfirmed, selectedReports]);
|
|
176
|
+
const handleDisconnect = (0, react_1.useCallback)(() => {
|
|
177
|
+
setSelectedCompany(null);
|
|
178
|
+
setSelectedReports([]);
|
|
179
|
+
setImportStepIndex(0);
|
|
180
|
+
setModalStep("closed");
|
|
181
|
+
if (onQboDisconnected)
|
|
182
|
+
onQboDisconnected();
|
|
183
|
+
}, [onQboDisconnected]);
|
|
184
|
+
const closeModal = (0, react_1.useCallback)(() => {
|
|
185
|
+
setModalStep("closed");
|
|
186
|
+
setError(null);
|
|
187
|
+
}, []);
|
|
188
|
+
const companies = selectedCompany ? [selectedCompany] : [];
|
|
189
|
+
return {
|
|
190
|
+
modalStep,
|
|
191
|
+
selectedCompany,
|
|
192
|
+
selectedReports,
|
|
193
|
+
selectedYear,
|
|
194
|
+
setSelectedYear,
|
|
195
|
+
importStepIndex,
|
|
196
|
+
error,
|
|
197
|
+
companies,
|
|
198
|
+
openPermissions,
|
|
199
|
+
openImport,
|
|
200
|
+
handlePermissionsContinue,
|
|
201
|
+
handleCompanySelected,
|
|
202
|
+
handleReportsConfirmed,
|
|
203
|
+
handleMappingConfirm,
|
|
204
|
+
handleDisconnect,
|
|
205
|
+
closeModal,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
@@ -83,7 +83,7 @@ function ClientParametersSection({ userContext = "expert", }) {
|
|
|
83
83
|
react_1.default.createElement("svg", { className: `transition-transform ${expanded ? "rotate-180" : "rotate-0"}`, width: "10", height: "10", viewBox: "0 0 12 12", fill: "none" },
|
|
84
84
|
react_1.default.createElement("path", { d: "M3 4.5l3 3 3-3", stroke: "#9498B8", strokeWidth: "1.5", strokeLinecap: "round" }))),
|
|
85
85
|
expanded && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
86
|
-
react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3" },
|
|
86
|
+
react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3" },
|
|
87
87
|
react_1.default.createElement("div", null,
|
|
88
88
|
react_1.default.createElement("label", { className: labelCls }, "Business Name"),
|
|
89
89
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "bizName", control: control, render: ({ field }) => (react_1.default.createElement("input", Object.assign({}, field, { placeholder: "Acme Consulting LLC", className: inputCls }))) }),
|
|
@@ -50,7 +50,7 @@ function CpaIntakeQuestionsSection({ userContext = "expert", }) {
|
|
|
50
50
|
react_1.default.createElement("svg", { className: `transition-transform ${expanded ? "rotate-180" : "rotate-0"}`, width: "10", height: "10", viewBox: "0 0 12 12", fill: "none" },
|
|
51
51
|
react_1.default.createElement("path", { d: "M3 4.5l3 3 3-3", stroke: "#9498B8", strokeWidth: "1.5", strokeLinecap: "round" }))),
|
|
52
52
|
expanded && (react_1.default.createElement("div", null,
|
|
53
|
-
react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3 mb-2.5" },
|
|
53
|
+
react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3 mb-2.5" },
|
|
54
54
|
react_1.default.createElement("div", null,
|
|
55
55
|
react_1.default.createElement("label", { className: labelCls }, "Overtime Premium Paid"),
|
|
56
56
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "overtimePremium", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
@@ -63,7 +63,7 @@ function CpaIntakeQuestionsSection({ userContext = "expert", }) {
|
|
|
63
63
|
react_1.default.createElement("input", Object.assign({}, field, { placeholder: "0", className: `${inputCls} pr-6` })),
|
|
64
64
|
react_1.default.createElement("span", { className: "absolute right-2.5 top-1/2 -translate-y-1/2 text-tax-axis-text-3 text-[13px]" }, "%"))) }),
|
|
65
65
|
industry !== "Restaurant / Hospitality" && (react_1.default.createElement("div", { className: helperCls }, "Typically relevant for Restaurant / Hospitality")))),
|
|
66
|
-
react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3 mb-2.5" },
|
|
66
|
+
react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3 mb-2.5" },
|
|
67
67
|
react_1.default.createElement("div", null,
|
|
68
68
|
react_1.default.createElement("label", { className: labelCls }, "Retirement Plan Contributions"),
|
|
69
69
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "retirementContributions", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
@@ -79,7 +79,7 @@ function CpaIntakeQuestionsSection({ userContext = "expert", }) {
|
|
|
79
79
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "hsaContributions", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
80
80
|
react_1.default.createElement("span", { className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-tax-axis-text-3 text-[13px]" }, "$"),
|
|
81
81
|
react_1.default.createElement("input", Object.assign({}, field, { placeholder: "0", className: `${inputCls} pl-6` })))) }))))),
|
|
82
|
-
react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3 mb-2.5" },
|
|
82
|
+
react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3 mb-2.5" },
|
|
83
83
|
react_1.default.createElement("div", null,
|
|
84
84
|
react_1.default.createElement("label", { className: labelCls }, "WOTC Qualifying Hires"),
|
|
85
85
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "wotcHires", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
@@ -8,7 +8,7 @@ const react_1 = __importDefault(require("react"));
|
|
|
8
8
|
const TaxAxisButton_1 = require("../shared/TaxAxisButton");
|
|
9
9
|
const TaxAxisBadge_1 = require("../shared/TaxAxisBadge");
|
|
10
10
|
function IntakeCtaCards({ onProspect, onFullAnalysis, userContext = "expert", }) {
|
|
11
|
-
return (react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3" },
|
|
11
|
+
return (react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3" },
|
|
12
12
|
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
13
|
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
14
|
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"),
|
|
@@ -70,7 +70,7 @@ function RefineAnalysisSection({ userContext: _userContext, }) {
|
|
|
70
70
|
expanded && (react_1.default.createElement("div", null,
|
|
71
71
|
react_1.default.createElement("div", { className: "text-[11px] text-tax-axis-text-4 italic mb-3" }, "These fields improve accuracy. Many will be auto-populated when documents are uploaded."),
|
|
72
72
|
react_1.default.createElement("div", { className: subHeaderCls, style: { marginTop: 0 } }, "Client Profile"),
|
|
73
|
-
react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3 mb-2.5" },
|
|
73
|
+
react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3 mb-2.5" },
|
|
74
74
|
react_1.default.createElement("div", null,
|
|
75
75
|
react_1.default.createElement("label", { className: labelCls }, "Filing Status"),
|
|
76
76
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "filingStatus", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
@@ -79,7 +79,7 @@ function RefineAnalysisSection({ userContext: _userContext, }) {
|
|
|
79
79
|
react_1.default.createElement("div", null,
|
|
80
80
|
react_1.default.createElement("label", { className: labelCls }, "Age"),
|
|
81
81
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "age", control: control, render: ({ field }) => (react_1.default.createElement("input", Object.assign({}, field, { type: "number", placeholder: "45", className: inputCls }))) }))),
|
|
82
|
-
react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3 mb-2.5" },
|
|
82
|
+
react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3 mb-2.5" },
|
|
83
83
|
react_1.default.createElement("div", null,
|
|
84
84
|
react_1.default.createElement("label", { className: labelCls }, "SSTB"),
|
|
85
85
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "sstb", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
@@ -91,7 +91,7 @@ function RefineAnalysisSection({ userContext: _userContext, }) {
|
|
|
91
91
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "ownsRealEstate", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
92
92
|
react_1.default.createElement("select", Object.assign({}, field, { className: selectCls }), REAL_ESTATE_OPTIONS.map(o => (react_1.default.createElement("option", { key: o, value: o, className: "bg-tax-axis-surface-2" }, o)))),
|
|
93
93
|
react_1.default.createElement(Chevron, null))) }))),
|
|
94
|
-
react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3 mb-2.5" },
|
|
94
|
+
react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3 mb-2.5" },
|
|
95
95
|
react_1.default.createElement("div", null,
|
|
96
96
|
react_1.default.createElement("label", { className: labelCls }, "Itemizes Deductions"),
|
|
97
97
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "itemizesDeductions", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
@@ -103,7 +103,7 @@ function RefineAnalysisSection({ userContext: _userContext, }) {
|
|
|
103
103
|
react_1.default.createElement("select", Object.assign({}, field, { className: selectCls }), SINGLE_OWNER_OPTIONS.map(o => (react_1.default.createElement("option", { key: o, value: o, className: "bg-tax-axis-surface-2" }, o)))),
|
|
104
104
|
react_1.default.createElement(Chevron, null))) }))),
|
|
105
105
|
react_1.default.createElement("div", { className: subHeaderCls }, "Financial Data"),
|
|
106
|
-
react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3 mb-2.5" },
|
|
106
|
+
react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3 mb-2.5" },
|
|
107
107
|
react_1.default.createElement("div", null,
|
|
108
108
|
react_1.default.createElement("label", { className: labelCls }, "Net Income"),
|
|
109
109
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "netIncome", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
@@ -114,7 +114,7 @@ function RefineAnalysisSection({ userContext: _userContext, }) {
|
|
|
114
114
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "equipmentPurchased", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
115
115
|
react_1.default.createElement("span", { className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-tax-axis-text-3 text-[13px]" }, "$"),
|
|
116
116
|
react_1.default.createElement("input", Object.assign({}, field, { placeholder: "0", className: `${inputCls} pl-6` })))) }))),
|
|
117
|
-
react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3 mb-2.5" },
|
|
117
|
+
react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3 mb-2.5" },
|
|
118
118
|
ownsRealEstate !== "No" ? (react_1.default.createElement("div", { className: "transition-opacity" },
|
|
119
119
|
react_1.default.createElement("label", { className: labelCls }, "Real Estate Value"),
|
|
120
120
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "realEstateValue", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
@@ -126,7 +126,7 @@ function RefineAnalysisSection({ userContext: _userContext, }) {
|
|
|
126
126
|
react_1.default.createElement("span", { className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-tax-axis-text-3 text-[13px]" }, "$"),
|
|
127
127
|
react_1.default.createElement("input", Object.assign({}, field, { placeholder: "0", className: `${inputCls} pl-6` })))) }))),
|
|
128
128
|
react_1.default.createElement("div", { className: subHeaderCls }, "Tax Profile"),
|
|
129
|
-
react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3 mb-2.5" },
|
|
129
|
+
react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3 mb-2.5" },
|
|
130
130
|
react_1.default.createElement("div", null,
|
|
131
131
|
react_1.default.createElement("label", { className: labelCls }, "Federal Marginal Rate"),
|
|
132
132
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "federalRate", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
@@ -137,7 +137,7 @@ function RefineAnalysisSection({ userContext: _userContext, }) {
|
|
|
137
137
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "stateRate", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
138
138
|
react_1.default.createElement("input", Object.assign({}, field, { placeholder: "4.95", className: `${inputCls} pr-6` })),
|
|
139
139
|
react_1.default.createElement("span", { className: "absolute right-2.5 top-1/2 -translate-y-1/2 text-tax-axis-text-3 text-[13px]" }, "%"))) }))),
|
|
140
|
-
react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3 mb-2.5" },
|
|
140
|
+
react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3 mb-2.5" },
|
|
141
141
|
react_1.default.createElement("div", null,
|
|
142
142
|
react_1.default.createElement("label", { className: labelCls }, "Tax Data Years Available"),
|
|
143
143
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "taxDataYears", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
@@ -27,12 +27,12 @@ function TaxAxisIntake({ userContext = "expert", onProspect, onFullAnalysis, ini
|
|
|
27
27
|
methods.handleSubmit((valid) => onFullAnalysis(valid), () => { })();
|
|
28
28
|
};
|
|
29
29
|
return (react_1.default.createElement(react_hook_form_1.FormProvider, Object.assign({}, methods),
|
|
30
|
-
react_1.default.createElement("div", { className: "grid grid-cols-
|
|
30
|
+
react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-5" },
|
|
31
31
|
react_1.default.createElement("div", { className: "flex flex-col gap-4" },
|
|
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
35
|
react_1.default.createElement(IntakeCtaCards_1.IntakeCtaCards, { onProspect: handleProspect, onFullAnalysis: handleFull, userContext: userContext })),
|
|
36
|
-
react_1.default.createElement("div", { className: "sticky top-5 self-start" },
|
|
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
|
}
|
|
@@ -26,10 +26,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
26
26
|
exports.TaxAxisProcessing = TaxAxisProcessing;
|
|
27
27
|
const react_1 = __importStar(require("react"));
|
|
28
28
|
const ProcessingStages_1 = require("./ProcessingStages");
|
|
29
|
-
const CRAWL_CEILING =
|
|
30
|
-
const CRAWL_TICK_MS =
|
|
31
|
-
const CRAWL_STEP = 0.
|
|
32
|
-
const FINISH_MS =
|
|
29
|
+
const CRAWL_CEILING = 99; // pause here until reportReady
|
|
30
|
+
const CRAWL_TICK_MS = 300; // interval between crawl ticks
|
|
31
|
+
const CRAWL_STEP = 0.27; // % added each tick → 0.27 / 0.3s = 0.9%/s
|
|
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
34
|
function buildStages(profile) {
|
|
35
35
|
var _a;
|
|
@@ -69,6 +69,30 @@ function TaxAxisProcessing({ onComplete, profile, reportReady = false, userConte
|
|
|
69
69
|
// Track current progress in a ref so the finish animation can read it synchronously
|
|
70
70
|
// without relying on the async functional-updater pattern.
|
|
71
71
|
const progressRef = (0, react_1.useRef)(0);
|
|
72
|
+
// ── Stall detection: show patience message after 35s of no progress change ──
|
|
73
|
+
const [showPatience, setShowPatience] = (0, react_1.useState)(false);
|
|
74
|
+
const lastProgressRef = (0, react_1.useRef)(0);
|
|
75
|
+
const stallTimerRef = (0, react_1.useRef)(null);
|
|
76
|
+
(0, react_1.useEffect)(() => {
|
|
77
|
+
if (finishingRef.current) {
|
|
78
|
+
setShowPatience(false);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (progress !== lastProgressRef.current) {
|
|
82
|
+
lastProgressRef.current = progress;
|
|
83
|
+
setShowPatience(false);
|
|
84
|
+
if (stallTimerRef.current)
|
|
85
|
+
clearTimeout(stallTimerRef.current);
|
|
86
|
+
stallTimerRef.current = setTimeout(() => {
|
|
87
|
+
if (!finishingRef.current)
|
|
88
|
+
setShowPatience(true);
|
|
89
|
+
}, 8000);
|
|
90
|
+
}
|
|
91
|
+
return () => {
|
|
92
|
+
if (stallTimerRef.current)
|
|
93
|
+
clearTimeout(stallTimerRef.current);
|
|
94
|
+
};
|
|
95
|
+
}, [progress]);
|
|
72
96
|
const setProgressSync = (v) => {
|
|
73
97
|
progressRef.current = v;
|
|
74
98
|
setProgress(v);
|
|
@@ -147,5 +171,6 @@ function TaxAxisProcessing({ onComplete, profile, reportReady = false, userConte
|
|
|
147
171
|
transition: isFinishing ? "none" : "width 0.18s linear",
|
|
148
172
|
} })),
|
|
149
173
|
react_1.default.createElement(ProcessingStages_1.ProcessingStages, { stages: stages, progress: stageIdx + 1 }),
|
|
150
|
-
pct >= CRAWL_CEILING && !isFinishing && (react_1.default.createElement("p", { className: "text-[11px] text-tax-axis-text-4 font-tax-axis-mono mt-4 animate-pulse" }, "Finalizing analysis\u2026"))
|
|
174
|
+
pct >= CRAWL_CEILING && !isFinishing && (react_1.default.createElement("p", { className: "text-[11px] text-tax-axis-text-4 font-tax-axis-mono mt-4 animate-pulse" }, "Finalizing analysis\u2026")),
|
|
175
|
+
showPatience && !isFinishing && (react_1.default.createElement("p", { className: "text-[13px] text-tax-axis-text-3 font-tax-axis-body mt-4 text-center" }, "Still running \u2014 document analysis typically takes 2\u20133 minutes. Do not navigate away."))));
|
|
151
176
|
}
|