npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

spartaxx-documents

v1.0.2

Published

Spartaxx client Documents module (Required / Submitted / Upload) — reusable across all Spartaxx internal applications

Readme

spartaxx-documents

Client Documents module — Required / Submitted / Upload — extracted from the Customer Service V2 web app so any Spartaxx internal application can mount it.

The package owns no endpoint knowledge. Every URL, controller, method, query id and package id is passed in by the host, so a second app points it at its own environment instead of forking the code.


Installation

npm install spartaxx-documents

Peer dependencies (most hosts already have all of these):

npm install react react-dom axios formik yup uuid \
            react-bootstrap react-select react-toastify react-icons react-lottie \
            lucide-react react-infinite-scroll-component

react-lottie is optional — it is only used by the "No Signature Required" empty state. Bootstrap 5 CSS must be loaded by the host; the module uses Bootstrap utility classes.

Import the stylesheet once, anywhere in your app:

import "spartaxx-documents/styles.css";

Quick start

import { DocumentsModule, type DocumentsConfig } from "spartaxx-documents";
import "spartaxx-documents/styles.css";

const documentsConfig: DocumentsConfig = {
  api: {
    baseUrl: process.env.REACT_APP_API_BASE_URL!,
    pfbaBaseUrl: process.env.REACT_APP_PFBA_API_BASE_URL!,
    paperwiseBaseUrl: process.env.REACT_APP_PAPERWISE_API_BASE_URL!,
    clientPackagePortalUrl: process.env.REACT_APP_CLIENT_PACKAGE_PORTAL_BASE_URL,
  },
  controllers: {
    dynamicQuery: "api/DynamicQuery",
    clientPackage: "clientpackage/clientpackage",
    paperwiseDocuments: "pwdocuments",
    hub: "hub/hub",
    hubShort: "hub",
  },
  methods: {
    executeQuery: "ExecuteQuery",
    buildClientPackage: "BuildClientPackage",
    previewClientPackage: "PreviewClientPackage",
    downloadClientPackage: "DownloadClientPackage",
    sendClientPackageEmail: "SendClientPackageEmail",
    sendClientPackageEmailPdf: "SendClientPackageEmailPdf",
    sendClientPackageUsMail: "SendClientPackageUsMail",
    sendClientPackageSms: "SendClientPackageSms",
    getPwDocuments: "GetPWDocuments",
    uploadPaperwiseDocument: "UploadPWDocuments",
    encrypt: "encrypt",
  },
  queryIds: {
    taxYears: 87,
    properties: 269,
    documentTypes: 96,
    correspondenceLog: 145,
  },
};

export const ClientDocuments = ({ rowData }) => (
  <DocumentsModule config={documentsConfig} rowData={rowData} />
);

That is the minimum. rowData may be omitted, in which case the client is decoded from the ?nekoT= URL token, exactly as the Customer Service app does today.


Two variants

| Variant | Use it for | Renders | |---|---|---| | variant="embedded" (default) | Mounting inside an existing page shell | bg-white rounded-3 shadow-sm p-1 documents-shell | | variant="iframe" | A standalone full-page route loaded in an iframe | Full-height shell, plus postMessage and query-param context |

The iframe variant accepts context by whichever of these arrives first:

  1. postMessage({ type: "CS_DOCUMENTS_INIT", payload }) from the parent frame (the constant is exported as DOCUMENTS_INIT_MESSAGE);
  2. ?rowData= / ?cookieResponse= URI-encoded JSON params;
  3. the ?nekoT= token.

Query string and hash are both checked, since some hosts route by hash. Pass allowedOrigins to restrict which parents may post context — the original module accepted messages from any origin.


Prop reference

DocumentsModule

| Prop | Type | Default | Notes | |---|---|---|---| | config | DocumentsConfig | required | Endpoints, ids. See below. | | variant | "embedded" \| "iframe" | "embedded" | | | rowData | DocumentsRowData \| null | decoded from token | ClientId / ClientNumber; either casing accepted. | | session | DocumentsSession \| null | resolved by adapter | Pre-resolved user. Supplies UserId and Email. | | sessionAdapter | DocumentsSessionAdapter | URL-token adapter | How to decode the token / resolve the user. | | token | string | ?nekoT= from the URL | Explicit token override. | | initialTab | "Required" \| "Submitted" \| "Upload" | "Required" | | | logger | DocumentsLogger | no-op | Same call shape as CS LogService. | | renderCorrespondence | ((rowData) => ReactNode) | false | built-in log | Omit for the built-in correspondence log. Pass a function to substitute your own, or false to hide the link. | | onSendToClient | (docs: SubmittedDocument[]) => Promise<void> \| void | — | Submitted tab's "Send to Client". See caveat below. | | onUploadSuccess | () => void | — | Fires after the module has already refreshed Submitted. | | allowedOrigins | string[] | any origin | variant="iframe" only. | | client | DocumentsClient | built from config | Reuse one client across mounts. |

config

| Field | Purpose | |---|---| | api.baseUrl | Client-package + hub API: preview, download, send, encrypt. | | api.pfbaBaseUrl | Dynamic-query API: tax years, properties, document types, logs. | | api.paperwiseBaseUrl | Paperwise: submitted-document listing and upload. | | api.clientPackagePortalUrl | Root used to build the signing URL. | | api.clientPortalApiBaseUrl | Optional; only getRequiredDocumentUrl uses it. | | api.requiredDocumentUrl | Fallback URL for the same call. | | controllers.*, methods.* | Path segments, joined as {base}/{controller}/{method}. | | queryIds.* | The six dynamic queries the module runs. | | paperwise.fileCabinetId | Fallback Paperwise cabinet. UAT 1015, production 1001. Correspondence rows carry their own FileCabinetId and that wins, so this only covers rows that omit it. Default 1001. | | paperwise.environmentId | Paperwise environment. Default "1001". | | packageIds.requiredDocuments | Parent package for the Required tree. Default 450314. | | packageIds.clientPackage | Parent package for preview/send. Default 450337. | | defaultTaxYear | Pre-selected year. Defaults to the current year. | | uploadIpAddress | Stamped on upload payloads. Defaults to "0.0.0.0". | | uploadBatchCodePrefix | Batch code prefix. Defaults to "CSV2_Upload_". |

Environment-specific values. packageIds and paperwise.fileCabinetId differ per environment: the Customer Service source carried UAT 450337 / PROD 450333 as a comment, and the Paperwise cabinet is 1015 in UAT versus 1001 in production. Set both explicitly per environment rather than relying on the defaults, which are the production values. PAPERWISE_FILE_CABINET_IDS is exported for convenience.


Correspondence log

The Required tab's Correspondence Logs link opens the client's send history: what went out, by which channel, to whom, the accounts behind each send, the stored Paperwise document, and a Resend action that rebuilds the package and re-emails it.

This is built in — no host wiring needed. It is also usable on its own:

import { CorrespondenceLogs, DocumentsProvider } from "spartaxx-documents";

<DocumentsProvider config={documentsConfig}>
  <CorrespondenceLogs rowData={rowData} mode="embedded" />
</DocumentsProvider>

| Prop | Type | Default | Notes | |---|---|---|---| | rowData | DocumentsRowData | null | required | Needs ClientId and ClientNumber. | | mode | "drawer" | "embedded" | "drawer" | "embedded" wraps it in a titled card. | | session | DocumentsSession | null | resolved by adapter | Supplies the resending user. | | initialLogs | CorrespondenceLogEntry[] | — | Seed the first page to skip a request. Replaces the Redux cache the CS version read from. |

To substitute your own view, or remove the link entirely:

<DocumentsModule config={cfg} renderCorrespondence={(rowData) => <MyLogs {...rowData} />} />
<DocumentsModule config={cfg} renderCorrespondence={false} />

Styling note

The log keeps the original Tailwind utility markup, which in the Customer Service app only renders because public/index.html loads the Tailwind Play CDN. To avoid pushing that dependency onto every consumer, the package ships the exact utility subset it needs, scoped under .spartaxx-correspondence so it cannot restyle a host's own .flex or .bg-white. It is generated, not hand-maintained:

npm run build:correspondence-css

Re-run that after changing any className in CorrespondenceLogs.tsx. Tailwind's preflight is off, so no global reset is injected into the host.


Session and logging

The module never imports the host's auth code. It asks a DocumentsSessionAdapter:

interface DocumentsSessionAdapter {
  decodeToken?: (token: string) => unknown | Promise<unknown>;
  getSession?: () => DocumentsSession | null | Promise<DocumentsSession | null>;
}

The default adapter reproduces the Spartaxx flow: base64-decode ?nekoT=, take its Guid, read the cookie stored under that GUID. Supply your own to plug in a different scheme:

<DocumentsModule
  config={documentsConfig}
  sessionAdapter={{
    decodeToken: (token) => myDecode(token),
    getSession: () => store.getState().auth.user,
  }}
/>

Or skip it entirely by passing rowData and session as props.

Logging is the same idea — an all-optional interface shaped like CS LogService, so that app can pass its logger straight through:

<DocumentsModule config={documentsConfig} logger={LogService} />

Headless use

Every API call is available without React:

import { createDocumentsClient, constructPackagePayload } from "spartaxx-documents";

const client = createDocumentsClient(documentsConfig, logger);

const years = await client.getTaxYears();
const tree = await client.buildRequiredDocuments({ clientId, clientNumber, userId });
await client.sendClientPackage("email", constructPackagePayload({ /* … */ }));

constructPackagePayload is pure — no config, no network — so hosts that assemble client packages elsewhere can import it on its own.

Error contract, kept identical to the original module so the UI behaves the same:

  • dynamic-query reads swallow failures, report via logger.logError, and resolve to [] (a failed read shows as an empty list, not an error);
  • writes and package operations reject, so callers can toast.

What changed in the extraction

Behaviour is a faithful port. These are the deliberate differences:

| Change | Why | |---|---| | react-router-dom is no longer needed | The tabs read the token from window.location instead of useSearchParams. Nothing is lost: the host patches the token with history.replaceState, which never notified react-router anyway. Pass token if you need reactivity. | | Correspondence is a render slot | Keeps the 990-line CorrespondenceDrawer and its own config couplings out of the package. The link disappears if you don't pass renderCorrespondence. | | defaultTaxYear defaults to the current year | The original hardcoded 2026 in five places. | | ipAddress defaults to "0.0.0.0" | The original shipped a hardcoded office IP. Set uploadIpAddress to restore a real value. | | Parent package ids are config | Were hardcoded 450314 / 450337 inline. | | One shared IndeterminateCheckbox | Was duplicated in the Required and Submitted tabs. | | Inline style objects became CSS classes | Overridable by hosts; see the classes appended to documents.css. | | Correspondence date-range picker removed | Unreachable in the original: setIsOpen(true) lived only in handleOpen, which was never called, so it could never open — and the query hardcoded DeliveryFromDate/DeliveryToDate to null regardless. Dropping it also removes the react-datepicker dependency and ~150 lines of picker CSS overrides. | | Correspondence document-viewer modal removed | Sat behind {false && …} with a hardcoded iframe URL. | | Correspondence Redux cache became initialLogs | The package carries no Redux dependency. | | Paperwise cabinet is taken from the row | The original hardcoded fileCabinetId: 1001 — the production cabinet — while running in UAT, where it is 1015, so document opens were requesting the wrong cabinet there. The client now prefers the FileCabinetId the correspondence row itself carries, falling back to config.paperwise.fileCabinetId. | | Dropped dead code | escAgentList (read from sessionStorage, never used), preparedFileBase64, preparedFileSignature, getFileSignature, and the four unused service exports. | | getSubmittedDocuments etc. take config | Were module-level constants read from the host's apiConfig. |

Known caveat carried over: the Submitted tab's "Send to Client" button had no API call in the original — it only showed a success toast. That is preserved. Pass onSendToClient to give it real behaviour; without it, the toast still appears and nothing is sent.


Wiring this into the Customer Service web app

The web app still contains its own copy at web/src/components/Documents. Nothing in web/ has been changed — this package stands alone. To switch it over:

1. Install the package

Build a tarball and install that. Prefer this over npm link — see the Testing section for why.

cd packages/spartaxx-documents
npm run build && npm pack            # -> spartaxx-documents-1.0.0.tgz

cd ../../web
npm install ../packages/spartaxx-documents/spartaxx-documents-1.0.0.tgz

2. Import the stylesheet once

In web/src/index.tsx:

import "spartaxx-documents/styles.css";

3. Map apiConfig onto DocumentsConfig

Every key below already exists in web/src/config/apiConfig.ts, so this is a straight copy. Save it as web/src/config/documentsConfig.ts:

import apiConfig from "./apiConfig";
import type { DocumentsConfig } from "spartaxx-documents";

export const documentsConfig: DocumentsConfig = {
  api: {
    baseUrl: apiConfig.api.baseUrl,
    pfbaBaseUrl: apiConfig.api.pfbaBaseUrl,
    paperwiseBaseUrl: apiConfig.api.paperwiseBaseUrl,
    clientPortalApiBaseUrl: apiConfig.api.clientPortalApiBaseUrl,
    clientPackagePortalUrl: apiConfig.urls.clientPackagePortal,
    requiredDocumentUrl: apiConfig.urls.requiredDocument,
  },
  controllers: {
    dynamicQuery: apiConfig.api.controllers.dynamicQuery,
    clientPackage: apiConfig.api.controllers.clientPackage,
    paperwiseDocuments: apiConfig.api.controllers.paperwiseDocuments,
    hub: apiConfig.api.controllers.hub,
    hubShort: apiConfig.api.controllers.hubShort,
  },
  methods: {
    executeQuery: apiConfig.api.methods.executeQuery,
    buildClientPackage: apiConfig.api.methods.buildClientPackage,
    previewClientPackage: apiConfig.api.methods.previewClientPackage,
    downloadClientPackage: apiConfig.api.methods.downloadClientPackage,
    sendClientPackageEmail: apiConfig.api.methods.sendClientPackageEmail,
    sendClientPackageEmailPdf: apiConfig.api.methods.sendClientPackageEmailPdf,
    sendClientPackageUsMail: apiConfig.api.methods.sendClientPackageUsMail,
    sendClientPackageSms: apiConfig.api.methods.sendClientPackageSms,
    getPwDocuments: apiConfig.api.methods.getPwDocuments,
    uploadPaperwiseDocument: apiConfig.api.methods.uploadPaperwiseDocument,
    getPwDocumentFile: apiConfig.api.methods.getPwDocumentFile,
    encrypt: apiConfig.api.methods.encrypt,
  },
  queryIds: {
    taxYears: apiConfig.api.queryIds.q87,
    properties: apiConfig.api.queryIds.q269,
    documentTypes: apiConfig.api.queryIds.q96,
    correspondenceLog: apiConfig.api.queryIds.q145,
    correspondenceAccounts: apiConfig.api.queryIds.q146,
    servicePackages: apiConfig.api.queryIds.q149,
  },
  // Environment-specific — see the warning under the config table.
  packageIds: { requiredDocuments: 450314, clientPackage: 450337 },
  paperwise: { fileCabinetId: 1015, environmentId: "1001" }, // 1015 UAT / 1001 prod
  defaultTaxYear: 2026,
};

4. Prove it on a throwaway route first

Add a temporary route before touching the real mounts:

import { DocumentsModule } from "spartaxx-documents";
import { documentsConfig } from "./config/documentsConfig";
import LogService from "./services/logger";

<Route
  path="/documents-test"
  element={<DocumentsModule config={documentsConfig} logger={LogService} />}
/>

Open it with a real ?nekoT= token from a live Customer Service session — the module decodes the client from it, exactly as the current code does.

5. Swap the real mounts

Replace <Documents /> and the /cs-documents route's <IframeDocuments /> with <DocumentsModule config={documentsConfig} logger={LogService} /> and <DocumentsModule variant="iframe" config={documentsConfig} logger={LogService} />.

The correspondence log is built in, so no renderCorrespondence is needed.

6. Repoint the two outside consumers

These import the module's service directly and will break when the folder is deleted:

  • web/src/components/Overview/Overview.tsx → buildClientPackage
  • web/src/Utills/RightSidebar/drawers/CorrespondenceDrawer.tsx → buildClientPackage, constructPackagePayload

constructPackagePayload is exported directly; buildClientPackage comes off createDocumentsClient(documentsConfig).

7. Delete the originals

web/src/components/Documents and web/src/common/constants/deliveryOptions.ts (folded into the package as deliveryOptions + client.sendClientPackage). Optionally also web/src/Utills/RightSidebar/drawers/CorrespondenceDrawer.tsx once nothing else uses it — note the drawer is still referenced by RightDrawerContent.tsx for the standalone correspondence panel, so check before removing it.


Testing

Three levels, cheapest first. Run them in this order when validating a change.

1. Static — types and public surface

npm run type-check      # tsc --noEmit over src/, strict
npm run build           # then:
npm run verify:dist     # compiles .verify/consumer.tsx against the built .d.ts

verify:dist is the one that catches a broken public API — it exercises DocumentsModule, DocumentsProvider, both variants, a custom session adapter and the headless client, all through the package's own entry point.

2. Interactive — the playground

npm run dev             # http://localhost:5174

Enter a ClientId and ClientNumber from a real UAT client, press Mount module, and click through all three tabs. The toolbar also toggles the embedded/iframe variants and renderCorrespondence, so you can confirm the Correspondence Logs link disappears when no renderer is passed. Every logger call is echoed to the browser console.

Two things the playground does deliberately:

  • API calls are proxied through the dev server (/uat-hub, /uat-cs, /uat-pw → the UAT hosts), so CORS never applies. Retarget with PLAYGROUND_HUB_API, PLAYGROUND_CS_API, PLAYGROUND_PW_API env vars.
  • Session comes from the form, not a cookie. The default adapter reads a cookie keyed by the token's GUID, and that cookie is scoped to the Customer Service domain — it does not exist on localhost. Passing rowData and session as props is the supported way to bypass token resolution, and this is what a host app with its own auth would do.

It imports from src/, so edits hot-reload.

3. Integration — install it into a host app

Build a real tarball and install that. Prefer this over npm link for the CRA-based web app: npm link exposes this package's own node_modules, which contains a second copy of React, and Webpack will then fail at runtime with "Invalid hook call / more than one copy of React". A tarball install pulls no devDependencies, so the duplicate cannot occur.

# in packages/spartaxx-documents
npm run build && npm pack           # -> spartaxx-documents-1.0.0.tgz

# in web/
npm install ../packages/spartaxx-documents/spartaxx-documents-1.0.0.tgz

Then import the stylesheet once, build a DocumentsConfig from apiConfig, and render <DocumentsModule … /> on a throwaway route before touching the real ones. See Wiring this into the Customer Service web app above.

To re-test after a code change: npm run build && npm pack again, then re-run the npm install line in web/ (npm caches by path, so reinstalling the same filename does pick up the new contents).


Development

npm install
npm run type-check   # tsc --noEmit, strict
npm run build        # vite lib build -> dist/{index.js,index.mjs,index.d.ts,style.css}

.verify/ and playground/ are development-only: both are excluded from src/, from npm run type-check, and from the published files (the tarball is dist/ + README, 27 files).

dist/index.esm.js is intentionally not whitespace-minified — Vite preserves /* @__PURE__ */ annotations in lib mode so the consuming bundler can tree-shake. The CJS

The ESM build is named .esm.js, not .mjs, on purpose: Webpack applies strict-ESM fullySpecified resolution to .mjs, which breaks bare subpath imports such as react-icons/fi (react-icons v4 ships no exports map). A .js file in a package without "type": "module" is resolved non-strictly, and bundlers still parse it as ESM because they follow the module/exports.import fields. Renaming it back to .mjs breaks react-scripts build in the Customer Service app. build is fully minified.