Skip to content

smit208/DeltaHarvest

Repository files navigation

DeltaHarvest · Tax-Loss Harvesting Simulator

A professional-grade React application built for the KoinX Frontend Internship Assessment — designed to help crypto investors optimise their tax liability through real-time loss-harvesting simulations.

🔗 GitHub →  |  🛠️ Built with React 18, TypeScript, Tailwind CSS v4


📸 Preview

Dashboard Preview


✨ Features

Feature Detail
Real-Time Tax Math STCG & LTCG recalculated instantly via a custom useTaxLogic hook as you select/deselect assets
"You're going to save ₹X" Savings banner appears only when pre-harvesting realised gains > post-harvesting gains — per spec
Amount to Sell column Populated with the asset's full holding when a row is selected; shows a read-only badge with a 🔒 icon
Scientific notation safety formatCryptoValue handles crypto dust values like 1.42e-14 that .toFixed() would silently zero
Select All / Deselect All Header checkbox with indeterminate state (some but not all selected)
Column sorting Click any header to sort by STCG, LTCG, Price, or Holdings — default is STCG ascending (worst losses first)
Shimmer loading skeletons Replaces missing data during the API delay instead of layout shift
Toast notifications Per-asset feedback when added/removed, collapsed to a summary for Select All
Keyboard navigation Every table row is focusable; Space/Enter toggles selection
Dark theme Full dark UI with glass-morphism cards, tabular-num font stack, and consistent colour tokens

🛠 Tech Stack

  • Framework: React 18 · Vite
  • Language: TypeScript (strict mode)
  • Styling: Tailwind CSS v4 (@theme custom tokens, no tailwind.config at runtime)
  • Icons: Lucide React
  • Utilities: clsx for conditional class management
  • No state management library — derived state via useMemo prevents data desync entirely

🧠 Technical Decisions

1. Derived state over synced state

Instead of maintaining two separate state objects for "Pre" and "Post" harvesting, useTaxLogic derives the post-harvest view from the pre-harvest base + the current selection set. This means there's a single source of truth — the selection map — and no risk of the two cards ever going out of sync.

2. Extracted pure math hook

All tax calculations live in src/hooks/useTaxLogic.ts and its exported helpers (getNetGain, getRealisedGains). This makes the financial logic independently testable without any React dependency. A vitest unit test can import and call it directly.

3. formatCryptoValue — no .toFixed()

Standard .toFixed(8) silently turns 1.42e-14 into "0.00000000", hiding a non-zero balance. The formatter uses toLocaleString for the normal range and falls back to toExponential for sub-1e-6 dust values.

4. Compound component table pattern

HoldingsTable is split into TableHeader, TableRow, and TableSkeleton sub-components. Each can be rendered and tested in isolation. The parent component is a coordinator — it only manages sort state and passes callbacks down.

5. Race condition guard in useHoldings

The data-fetch effect uses a didCancel boolean (not AbortController — YAGNI for a mock API) to ignore stale responses if the component unmounts mid-fetch. This prevents the setState on unmounted component warning in React strict mode.


🚀 Local Setup

# Install dependencies
npm install

# Start dev server
npm run dev

# Open http://localhost:5173

Requires Node.js ≥ 18.


📁 Project Structure

src/
├── components/
│   ├── Dashboard.tsx          # Main page — wires hooks to UI
│   ├── GainsSummaryCard.tsx   # Pre / After Harvesting cards
│   ├── HarvestBanner.tsx      # "You're going to save ₹X" banner
│   ├── Toast.tsx              # Notification stack
│   └── HoldingsTable/
│       ├── index.tsx          # Table coordinator + sort state
│       ├── TableHeader.tsx    # Sticky thead with sortable columns
│       ├── TableRow.tsx       # Individual asset row + Amount to Sell cell
│       └── TableSkeleton.tsx  # Shimmer loading rows
├── hooks/
│   ├── useTaxLogic.ts         # Pure tax calculation hook
│   ├── useHoldings.ts         # Data fetch + selection state
│   └── useToast.ts            # Lightweight toast queue
├── services/
│   └── portfolioApi.ts        # Mock API with simulated network delay
├── data/
│   └── mockData.ts            # Realistic INR-denominated portfolio data
├── types/
│   └── index.ts               # Domain types: HoldingAsset, CapitalGainsReport
└── utils/
    └── formatters.ts          # Currency, crypto, and date formatters

💡 What I'd add next

  • Unit tests via vitest + @testing-library/react — the pure hooks are already structured for this
  • PersistencelocalStorage effect in useHoldings to survive page refresh
  • WebSocket mock — simulates live price updates to make the demo more dynamic
  • Export to CSV — download the harvesting plan as a spreadsheet for a CA

Built by Smit Prajapati · KoinX Frontend Internship Assessment · April 2026

About

Tax-Loss Harvesting simulator built for the KoinX Frontend Internship. Real-time STCG/LTCG calculation, interactive holdings table, and smart tax-savings estimation — React 18 + TypeScript + Tailwind CSS v4.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors