Skip to content

feat(investor): redesign campaigns UI, invest dialog, and route structure#60

Merged
JoelVR17 merged 3 commits intoTrustless-Work:developfrom
andreMD287:develop
Mar 12, 2026
Merged

feat(investor): redesign campaigns UI, invest dialog, and route structure#60
JoelVR17 merged 3 commits intoTrustless-Work:developfrom
andreMD287:develop

Conversation

@andreMD287
Copy link
Contributor

@andreMD287 andreMD287 commented Mar 12, 2026

Summary

  • Campaigns route (/campaigns): Moved the "Invest in Campaigns" view from the home page to /campaigns with its own layout and page, reusing RoiDashboardShell, RoiHeader, and CampaignToolbar components.
  • Invest dialog redesign: Larger dialog with styled USDC input, available balance display, Estimated Yield / Term Length cards, real-time investment return calculator, disclaimer box, and full-width "Confirm Investment" button.
  • Route cleanup: Root / redirects to /campaigns. Layout simplified to only providers. Fixed z-index conflict that was hiding the dialog behind the shell overlay.
  • Card redesign: ProjectCard updated with teal "FUNDRAISING" badge, vertical layout, and orange "Invest" button. ProjectList now accepts search/filter props with client-side filtering.
  • Hydration fix: CampaignToolbar dynamically imported with ssr: false to prevent Radix Select ID mismatch between server and client.

Test plan

  • Navigate to localhost:3001 → should redirect to /campaigns
  • /campaigns shows sidebar, header with search/bell, filter dropdown, and project cards
  • Click "Invest" button → dialog opens with USDC input, yield/term cards, return calculator, disclaimer
  • Type an amount → return calculator updates in real-time
  • Submit investment → success screen with transaction details
  • /roi page works with same shell layout
  • No hydration mismatch warnings in console

Summary by CodeRabbit

  • New Features

    • Added a dedicated Campaigns page with search and filter controls
    • Introduced a left-side navigation sidebar for quick access
    • Added project cards with fundraising stats and an integrated investment flow
  • Improvements

    • Redesigned investment dialog with clearer financial summaries and simplified confirmation
    • Streamlined app layout and routing (default redirects to campaigns)
    • Project list now filters and loads data dynamically for responsive browsing

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6f768845-e0f5-4edd-9314-f651d42d4ecd

📥 Commits

Reviewing files that changed from the base of the PR and between 8048c8c and 8080a57.

⛔ Files ignored due to path filters (72)
  • apps/core/dist/app.controller.d.ts is excluded by !**/dist/**
  • apps/core/dist/app.controller.js is excluded by !**/dist/**
  • apps/core/dist/app.controller.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/app.module.d.ts is excluded by !**/dist/**
  • apps/core/dist/app.module.js is excluded by !**/dist/**
  • apps/core/dist/app.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/app.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/app.service.js is excluded by !**/dist/**
  • apps/core/dist/app.service.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/campaigns/campaigns.controller.d.ts is excluded by !**/dist/**
  • apps/core/dist/campaigns/campaigns.controller.js is excluded by !**/dist/**
  • apps/core/dist/campaigns/campaigns.controller.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/campaigns/campaigns.module.d.ts is excluded by !**/dist/**
  • apps/core/dist/campaigns/campaigns.module.js is excluded by !**/dist/**
  • apps/core/dist/campaigns/campaigns.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/campaigns/campaigns.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/campaigns/campaigns.service.js is excluded by !**/dist/**
  • apps/core/dist/campaigns/campaigns.service.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/campaigns/dto/create-campaign.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/campaigns/dto/create-campaign.dto.js is excluded by !**/dist/**
  • apps/core/dist/campaigns/dto/create-campaign.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/campaigns/dto/update-campaign-status.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/campaigns/dto/update-campaign-status.dto.js is excluded by !**/dist/**
  • apps/core/dist/campaigns/dto/update-campaign-status.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/campaigns/dto/update-campaign.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/campaigns/dto/update-campaign.dto.js is excluded by !**/dist/**
  • apps/core/dist/campaigns/dto/update-campaign.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/deploy/deploy.controller.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.controller.js is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.controller.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/deploy/deploy.module.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.module.js is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/deploy/deploy.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.service.js is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.service.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/deploy/dto/deploy-participation-token.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/dto/deploy-participation-token.dto.js is excluded by !**/dist/**
  • apps/core/dist/deploy/dto/deploy-participation-token.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/deploy/dto/deploy-token-factory.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/dto/deploy-token-factory.dto.js is excluded by !**/dist/**
  • apps/core/dist/deploy/dto/deploy-token-factory.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/deploy/dto/deploy-vault.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/dto/deploy-vault.dto.js is excluded by !**/dist/**
  • apps/core/dist/deploy/dto/deploy-vault.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/investments/dto/create-investment.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/investments/dto/create-investment.dto.js is excluded by !**/dist/**
  • apps/core/dist/investments/dto/create-investment.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/investments/investments.controller.d.ts is excluded by !**/dist/**
  • apps/core/dist/investments/investments.controller.js is excluded by !**/dist/**
  • apps/core/dist/investments/investments.controller.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/investments/investments.module.d.ts is excluded by !**/dist/**
  • apps/core/dist/investments/investments.module.js is excluded by !**/dist/**
  • apps/core/dist/investments/investments.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/investments/investments.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/investments/investments.service.js is excluded by !**/dist/**
  • apps/core/dist/investments/investments.service.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/main.d.ts is excluded by !**/dist/**
  • apps/core/dist/main.js is excluded by !**/dist/**
  • apps/core/dist/main.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/prisma/prisma.module.d.ts is excluded by !**/dist/**
  • apps/core/dist/prisma/prisma.module.js is excluded by !**/dist/**
  • apps/core/dist/prisma/prisma.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/prisma/prisma.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/prisma/prisma.service.js is excluded by !**/dist/**
  • apps/core/dist/prisma/prisma.service.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/soroban/soroban.module.d.ts is excluded by !**/dist/**
  • apps/core/dist/soroban/soroban.module.js is excluded by !**/dist/**
  • apps/core/dist/soroban/soroban.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/soroban/soroban.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/soroban/soroban.service.js is excluded by !**/dist/**
  • apps/core/dist/soroban/soroban.service.js.map is excluded by !**/dist/**, !**/*.map
📒 Files selected for processing (1)
  • .gitignore

📝 Walkthrough

Walkthrough

Adds a campaigns section and sidebar, simplifies root layout, redirects home to /campaigns, introduces CampaignsLayout wrapping children with RoiDashboardShell, refactors project listing to fetch/filter escrows, and updates InvestDialog and ROI shell assets and layout.

Changes

Cohort / File(s) Summary
App Layout & Routing
apps/investor-tokenization/src/app/layout.tsx, apps/investor-tokenization/src/app/page.tsx
Root layout simplified (Header/FloatingDock removed); home page now redirects to /campaigns.
Campaigns feature
apps/investor-tokenization/src/app/campaigns/layout.tsx, apps/investor-tokenization/src/app/campaigns/page.tsx
New CampaignsLayout wraps children with RoiDashboardShell; new client-side CampaignsPage with local search/filter state, dynamic CampaignToolbar, and ProjectList composition.
Sidebar & navigation
apps/investor-tokenization/src/components/shared/Sidebar.tsx
New client-side Sidebar component: fixed left rail, logo, hard-coded nav links with active detection, WalletButton, and profile card.
ROI shell & assets
apps/investor-tokenization/src/features/roi/roi-dashboard-shell.tsx
Nav default route changed to /campaigns; container z-index lowered; logo source updated to /interactuar_logo.png.
Home / ROI pages
apps/investor-tokenization/src/features/home/HomeView.tsx, apps/investor-tokenization/src/app/roi/page.tsx
HomeView refactored with two-column header, search and filter state passed to ProjectList; ROI page uses dynamic import for CampaignToolbar (ssr: false).
Project UI & data
apps/investor-tokenization/src/features/transparency/ProjectCard.tsx, apps/investor-tokenization/src/features/transparency/ProjectList.tsx
Added ProjectCard (with getLoansCompleted, getMinInvest) and refactored ProjectList to fetch escrows via useQuery, map escrows by id, and apply search/filter before rendering ProjectCards.
Invest Dialog
apps/investor-tokenization/src/features/tokens/components/InvestDialog.tsx
Replaced trigger with custom Button (Rocket icon); adjusted dialog sizing and success layout; added investment summary (yield, term, estimated return), simplified form and single confirm button; removed RainbowButton.
Misc
.gitignore
Added recursive **/dist ignore rule.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client as Client (Browser)
  participant Campaigns as CampaignsPage
  participant Shell as RoiDashboardShell
  participant Toolbar as CampaignToolbar (dynamic)
  participant List as ProjectList
  participant Indexer as Escrow Indexer API
  participant Dialog as InvestDialog

  Client->>Campaigns: Navigate to /campaigns
  Campaigns->>Shell: Render children
  Campaigns->>Toolbar: Load dynamic toolbar (ssr:false)
  Campaigns->>List: Provide search/filter props
  List->>Indexer: Fetch escrows by contract IDs
  Indexer-->>List: Return escrows
  List->>List: Filter/memoize escrows
  List->>ProjectCard: Render items
  Client->>Dialog: Open InvestDialog (via ProjectCard)
  Dialog->>Dialog: Compute estimatedReturn, totalAtMaturity
  Dialog-->>Client: Submit investment
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • JoelVR17

Poem

🐰🌱
I hopped through routes and sidebar rows,
Planted filters where the campaign grows,
With Rocket button, projects shine,
Search and launch — a carrot-line! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main changes: UI redesign for campaigns, invest dialog updates, and routing structure modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan for PR comments
  • Generate coding plan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (6)
apps/investor-tokenization/src/app/campaigns/page.tsx (1)

16-25: Move this page composition behind a feature entrypoint.

This route now owns local UI state and rendering that belongs in src/features, and it overlaps with the HomeView composition changed in the same PR. A dedicated CampaignsView feature component would keep page.tsx as a thin wrapper and avoid drift. As per coding guidelines, "Organize Next.js frontends using feature-based folder structure in src/features/".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/app/campaigns/page.tsx` around lines 16 - 25,
The CampaignsPage currently owns local UI state and composition (useState,
RoiHeader, CampaignToolbar, ProjectList); extract this composition into a new
feature component (e.g., CampaignsView) under src/features so the view owns the
state (search, filter) and renders <RoiHeader />, <CampaignToolbar /> and
<ProjectList />; then simplify page.tsx to import and render the new
CampaignsView as the default export, and update any imports/usages accordingly
so state and rendering live in the feature folder and page.tsx remains a thin
entrypoint.
apps/investor-tokenization/src/app/roi/page.tsx (1)

17-35: Keep the route thin and move the view logic into src/features.

search/filter state plus the campaign filtering logic now live in the route file. Extract this into a feature-level view component and let page.tsx just render it, so the App Router layer stays declarative. As per coding guidelines, "Organize Next.js frontends using feature-based folder structure in src/features/".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/app/roi/page.tsx` around lines 17 - 35, The
route file's state and view logic (useState hooks search/setSearch and
filter/setFilter plus the useMemo filteredCampaigns that references
mockCampaigns) should be extracted into a feature component under src/features
(e.g. create src/features/roi/RoiView or RoiFeature) that owns the UI and
filtering logic; update page.tsx (RoiPage) to be thin and only import and render
the new component (pass props only if needed), move the filtering code and
mockCampaigns usage into the new component (keeping function/component names
like RoiView or RoiFeature and the filteredCampaigns logic intact), export the
feature component as default or named export and remove the state/useMemo from
RoiPage so the App Router route stays declarative.
apps/investor-tokenization/src/components/shared/Sidebar.tsx (1)

15-26: Centralize the sidebar nav config instead of hard-coding a second copy here.

These links now duplicate the ROI_NAV_ITEMS already defined in apps/investor-tokenization/src/features/roi/roi-dashboard-shell.tsx, which makes labels/routes easy to desynchronize. Export a shared nav model from src/features and consume it from both sidebar shells. As per coding guidelines, "Organize Next.js frontends using feature-based folder structure in src/features/".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/components/shared/Sidebar.tsx` around lines 15
- 26, The Sidebar component currently hard-codes a local links array (const
links: SidebarLink[]) duplicating ROI_NAV_ITEMS from roi-dashboard-shell.tsx;
instead, move the nav model into a shared export under src/features (e.g.,
export const ROI_NAV_ITEMS or a generic NAV_ITEMS in src/features/roi/nav.ts),
import that shared constant in both
apps/investor-tokenization/src/components/shared/Sidebar.tsx and
apps/investor-tokenization/src/features/roi/roi-dashboard-shell.tsx, and replace
the local links usage with the imported shared nav to keep labels/routes
centralized and avoid desynchronization.
apps/investor-tokenization/src/features/home/HomeView.tsx (1)

13-13: Use the @/ alias for this internal import.

../transparency/ProjectList breaks the app’s import convention and makes moves/refactors noisier. Switch it to @/features/transparency/ProjectList. As per coding guidelines, "Use path alias @/* mapping to ./src/* for imports".

Suggested change
-import { ProjectList } from "../transparency/ProjectList";
+import { ProjectList } from "@/features/transparency/ProjectList";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/features/home/HomeView.tsx` at line 13,
Replace the relative import in HomeView.tsx that pulls ProjectList from
"../transparency/ProjectList" with the project path alias import
"@/features/transparency/ProjectList" so the file imports ProjectList using the
`@/`* mapping; update the import statement in HomeView.tsx accordingly to follow
the codebase convention.
apps/investor-tokenization/src/features/transparency/ProjectList.tsx (1)

3-7: Use the repo path alias for ProjectCard.

This new local import bypasses the frontend @/* convention.

♻️ Suggested change
-import { ProjectCard } from "./ProjectCard";
+import { ProjectCard } from "@/features/transparency/ProjectCard";
As per coding guidelines, "Use path alias `@/*` mapping to `./src/*` for imports".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/features/transparency/ProjectList.tsx` around
lines 3 - 7, Replace the local relative import of ProjectCard with the
repository path-alias import (use the `@/`* mapping to ./src/*) in
ProjectList.tsx; locate the import statement referencing ProjectCard and change
it to use the alias (e.g., import { ProjectCard } from
"@/features/transparency/ProjectCard") so the project follows the frontend
path-alias convention.
apps/investor-tokenization/src/features/transparency/ProjectCard.tsx (1)

3-5: Pull shared primitives from @repo/ui.

This new feature file is importing Card, Badge, and Button from @tokenization/ui, which diverges from the shared UI package the repo standardizes on.

As per coding guidelines, "Import shared UI components from @repo/ui package (button, card, etc.)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/features/transparency/ProjectCard.tsx` around
lines 3 - 5, The file imports shared UI primitives from the wrong package;
replace imports of Card, CardContent, Badge, and Button from "@tokenization/ui"
with the standardized shared package "@repo/ui". Update the import statements
that reference the symbols Card, CardContent, Badge, and Button so the
components come from "@repo/ui" instead of "@tokenization/ui" and ensure any
usage of these symbols in ProjectCard.tsx remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/investor-tokenization/src/components/shared/Sidebar.tsx`:
- Around line 53-55: The active-route check in the isActive assignment
incorrectly uses pathname?.startsWith(link.href) and will mark sibling routes
(e.g., /campaigns-old) as active; change the condition to match either exact
equality or a path boundary by using pathname === link.href ||
pathname?.startsWith(link.href + '/') (or equivalent check that ensures a
trailing '/' boundary) so that only the exact route or a subpath under that
route sets isActive; update the isActive assignment (where pathname and
link.href are referenced) accordingly.

In `@apps/investor-tokenization/src/features/home/HomeView.tsx`:
- Around line 39-44: The notifications button in HomeView (the <button> that
renders the Bell icon) is missing an accessible name; add an
aria-label="Notifications" attribute to that button element so screen readers
can identify it (mirror the approach used in RoiHeader for accessible icon-only
buttons).

In `@apps/investor-tokenization/src/features/tokens/components/InvestDialog.tsx`:
- Around line 334-341: The "Available balance" row in InvestDialog is showing
totalAmount (campaign milestones sum) instead of the investor's spendable wallet
or remaining investable amount; update the UI to either (A) display the actual
investor balance by replacing totalAmount with the correct value (e.g.
walletBalance, spendableBalance, or remainingAmount retrieved from the existing
balance hook/prop or by calling the balance fetching function used elsewhere),
or (B) if you intend to keep showing campaign target, rename the label from
"Available balance" to something like "Campaign target" to match totalAmount;
locate the span rendering totalAmount in the InvestDialog component and change
the bound variable or the label accordingly.

In `@apps/investor-tokenization/src/features/transparency/ProjectCard.tsx`:
- Around line 39-50: ProjectCard currently falls back to defaults and still
renders an actionable InvestDialog when tokenSale exists even if escrow is
missing; update the rendering logic so InvestDialog (and any invest CTA/trigger)
is only rendered and enabled when escrow is non-null and contains the required
campaign data (e.g., escrow.title/escrow.description/escrow.trustline) and when
isLoading is false; use the ProjectCard props (escrow, tokenSale, escrowId,
isLoading) to gate the InvestDialog rendering and disable the CTA otherwise
(also fix the same guard where InvestDialog is rendered around lines 106-125).

In `@apps/investor-tokenization/src/features/transparency/ProjectList.tsx`:
- Around line 57-70: filteredData's status filtering treats missing escrows as
"fundraising" and hides unresolved items for "active"; update the useMemo filter
to explicitly handle undefined escrows: use escrowsById lookup (escrow) and if
escrow is null/undefined return true so unresolved items are not excluded by any
status filter, change the "active" case to require escrow?.isActive === true
(but allow unresolved to pass through) and change the "fundraising" case to
require escrow?.isActive === false (do NOT use !escrow?.isActive), referencing
the filteredData useMemo, escrowsById, search and filter variables to locate and
fix the logic.

---

Nitpick comments:
In `@apps/investor-tokenization/src/app/campaigns/page.tsx`:
- Around line 16-25: The CampaignsPage currently owns local UI state and
composition (useState, RoiHeader, CampaignToolbar, ProjectList); extract this
composition into a new feature component (e.g., CampaignsView) under
src/features so the view owns the state (search, filter) and renders <RoiHeader
/>, <CampaignToolbar /> and <ProjectList />; then simplify page.tsx to import
and render the new CampaignsView as the default export, and update any
imports/usages accordingly so state and rendering live in the feature folder and
page.tsx remains a thin entrypoint.

In `@apps/investor-tokenization/src/app/roi/page.tsx`:
- Around line 17-35: The route file's state and view logic (useState hooks
search/setSearch and filter/setFilter plus the useMemo filteredCampaigns that
references mockCampaigns) should be extracted into a feature component under
src/features (e.g. create src/features/roi/RoiView or RoiFeature) that owns the
UI and filtering logic; update page.tsx (RoiPage) to be thin and only import and
render the new component (pass props only if needed), move the filtering code
and mockCampaigns usage into the new component (keeping function/component names
like RoiView or RoiFeature and the filteredCampaigns logic intact), export the
feature component as default or named export and remove the state/useMemo from
RoiPage so the App Router route stays declarative.

In `@apps/investor-tokenization/src/components/shared/Sidebar.tsx`:
- Around line 15-26: The Sidebar component currently hard-codes a local links
array (const links: SidebarLink[]) duplicating ROI_NAV_ITEMS from
roi-dashboard-shell.tsx; instead, move the nav model into a shared export under
src/features (e.g., export const ROI_NAV_ITEMS or a generic NAV_ITEMS in
src/features/roi/nav.ts), import that shared constant in both
apps/investor-tokenization/src/components/shared/Sidebar.tsx and
apps/investor-tokenization/src/features/roi/roi-dashboard-shell.tsx, and replace
the local links usage with the imported shared nav to keep labels/routes
centralized and avoid desynchronization.

In `@apps/investor-tokenization/src/features/home/HomeView.tsx`:
- Line 13: Replace the relative import in HomeView.tsx that pulls ProjectList
from "../transparency/ProjectList" with the project path alias import
"@/features/transparency/ProjectList" so the file imports ProjectList using the
`@/`* mapping; update the import statement in HomeView.tsx accordingly to follow
the codebase convention.

In `@apps/investor-tokenization/src/features/transparency/ProjectCard.tsx`:
- Around line 3-5: The file imports shared UI primitives from the wrong package;
replace imports of Card, CardContent, Badge, and Button from "@tokenization/ui"
with the standardized shared package "@repo/ui". Update the import statements
that reference the symbols Card, CardContent, Badge, and Button so the
components come from "@repo/ui" instead of "@tokenization/ui" and ensure any
usage of these symbols in ProjectCard.tsx remains unchanged.

In `@apps/investor-tokenization/src/features/transparency/ProjectList.tsx`:
- Around line 3-7: Replace the local relative import of ProjectCard with the
repository path-alias import (use the `@/`* mapping to ./src/*) in
ProjectList.tsx; locate the import statement referencing ProjectCard and change
it to use the alias (e.g., import { ProjectCard } from
"@/features/transparency/ProjectCard") so the project follows the frontend
path-alias convention.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f8e715cf-f90b-4489-aaa0-4a84bb8f4cba

📥 Commits

Reviewing files that changed from the base of the PR and between ba6f7d5 and 8048c8c.

⛔ Files ignored due to path filters (18)
  • apps/core/dist/app.module.js is excluded by !**/dist/**
  • apps/core/dist/app.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/loans/dto/create-loan.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/loans/dto/create-loan.dto.js is excluded by !**/dist/**
  • apps/core/dist/loans/dto/create-loan.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/loans/dto/update-loan.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/loans/dto/update-loan.dto.js is excluded by !**/dist/**
  • apps/core/dist/loans/dto/update-loan.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/loans/loans.controller.d.ts is excluded by !**/dist/**
  • apps/core/dist/loans/loans.controller.js is excluded by !**/dist/**
  • apps/core/dist/loans/loans.controller.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/loans/loans.module.d.ts is excluded by !**/dist/**
  • apps/core/dist/loans/loans.module.js is excluded by !**/dist/**
  • apps/core/dist/loans/loans.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/loans/loans.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/loans/loans.service.js is excluded by !**/dist/**
  • apps/core/dist/loans/loans.service.js.map is excluded by !**/dist/**, !**/*.map
  • apps/investor-tokenization/public/interactuar_logo.png is excluded by !**/*.png
📒 Files selected for processing (11)
  • apps/investor-tokenization/src/app/campaigns/layout.tsx
  • apps/investor-tokenization/src/app/campaigns/page.tsx
  • apps/investor-tokenization/src/app/layout.tsx
  • apps/investor-tokenization/src/app/page.tsx
  • apps/investor-tokenization/src/app/roi/page.tsx
  • apps/investor-tokenization/src/components/shared/Sidebar.tsx
  • apps/investor-tokenization/src/features/home/HomeView.tsx
  • apps/investor-tokenization/src/features/roi/roi-dashboard-shell.tsx
  • apps/investor-tokenization/src/features/tokens/components/InvestDialog.tsx
  • apps/investor-tokenization/src/features/transparency/ProjectCard.tsx
  • apps/investor-tokenization/src/features/transparency/ProjectList.tsx

Comment on lines +53 to +55
const isActive =
pathname === link.href || pathname?.startsWith(link.href);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tighten the active-route match so sibling paths don't light up the wrong tab.

pathname?.startsWith(link.href) will also mark /campaigns-old or /roi-metrics as active. Match on the exact path or a / boundary instead.

Suggested change
-          const isActive =
-            pathname === link.href || pathname?.startsWith(link.href);
+          const isActive =
+            pathname === link.href || pathname.startsWith(`${link.href}/`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isActive =
pathname === link.href || pathname?.startsWith(link.href);
const isActive =
pathname === link.href || pathname?.startsWith(`${link.href}/`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/components/shared/Sidebar.tsx` around lines 53
- 55, The active-route check in the isActive assignment incorrectly uses
pathname?.startsWith(link.href) and will mark sibling routes (e.g.,
/campaigns-old) as active; change the condition to match either exact equality
or a path boundary by using pathname === link.href ||
pathname?.startsWith(link.href + '/') (or equivalent check that ensures a
trailing '/' boundary) so that only the exact route or a subpath under that
route sets isActive; update the isActive assignment (where pathname and
link.href are referenced) accordingly.

Comment on lines +39 to +44
<button
type="button"
className="relative rounded-md p-2 hover:bg-accent"
>
<Bell className="h-5 w-5 text-muted-foreground" />
</button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add an accessible name to the icon-only notifications button.

This button renders only the bell icon, so screen readers won't get a usable label. Add aria-label="Notifications" like the RoiHeader version does.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/features/home/HomeView.tsx` around lines 39 -
44, The notifications button in HomeView (the <button> that renders the Bell
icon) is missing an accessible name; add an aria-label="Notifications" attribute
to that button element so screen readers can identify it (mirror the approach
used in RoiHeader for accessible icon-only buttons).

Comment on lines +334 to +341
<p className="text-xs text-muted-foreground">
Available balance:{" "}
<span className="font-medium">
{totalAmount > 0
? `${totalAmount.toLocaleString("en-US", { minimumFractionDigits: 2 })} ${currency}`
: `0.00 ${currency}`}
</span>
</p>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Available balance is showing the wrong value.

totalAmount is the sum of the campaign milestones, so this row is currently displaying the campaign target — not the connected wallet balance and not the remaining investable amount. Either fetch the investor's spendable balance here or rename the label to match the number being shown.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/features/tokens/components/InvestDialog.tsx`
around lines 334 - 341, The "Available balance" row in InvestDialog is showing
totalAmount (campaign milestones sum) instead of the investor's spendable wallet
or remaining investable amount; update the UI to either (A) display the actual
investor balance by replacing totalAmount with the correct value (e.g.
walletBalance, spendableBalance, or remainingAmount retrieved from the existing
balance hook/prop or by calling the balance fetching function used elsewhere),
or (B) if you intend to keep showing campaign target, rename the label from
"Available balance" to something like "Campaign target" to match totalAmount;
locate the span rendering totalAmount in the InvestDialog component and change
the bound variable or the label accordingly.

Comment on lines +39 to +50
export const ProjectCard = ({
escrow,
escrowId,
tokenSale,
imageSrc,
isLoading = false,
}: ProjectCardProps) => {
const title = escrow?.title ?? "Loading...";
const description = escrow?.description ?? "";
const loansCompleted = getLoansCompleted(escrow);
const minInvest = getMinInvest(escrow);
const currency = escrow?.trustline?.symbol ?? "USDC";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Disable the invest flow until escrow details are present.

After loading, this component falls back to placeholder copy/default values when escrow is missing, but it still renders a live InvestDialog whenever tokenSale exists. A failed or partial lookup should not leave the investment CTA actionable against incomplete campaign data.

🛡️ Minimal guard
 export const ProjectCard = ({
   escrow,
   escrowId,
   tokenSale,
   imageSrc,
   isLoading = false,
 }: ProjectCardProps) => {
+  const hasEscrow = !!escrow;
   const title = escrow?.title ?? "Loading...";
   const description = escrow?.description ?? "";
   const loansCompleted = getLoansCompleted(escrow);
   const minInvest = getMinInvest(escrow);
   const currency = escrow?.trustline?.symbol ?? "USDC";
@@
-            {tokenSale ? (
+            {tokenSale && hasEscrow ? (
               <SelectedEscrowProvider
                 value={{
                   escrow,
                   escrowId,
                   tokenSaleContractId: tokenSale,
                   imageSrc,
                 }}
               >
                 <InvestDialog tokenSaleContractId={tokenSale} />
               </SelectedEscrowProvider>
             ) : (
               <Button
                 disabled
                 className="bg-orange-500 text-white hover:bg-orange-600"
               >

Also applies to: 106-125

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/features/transparency/ProjectCard.tsx` around
lines 39 - 50, ProjectCard currently falls back to defaults and still renders an
actionable InvestDialog when tokenSale exists even if escrow is missing; update
the rendering logic so InvestDialog (and any invest CTA/trigger) is only
rendered and enabled when escrow is non-null and contains the required campaign
data (e.g., escrow.title/escrow.description/escrow.trustline) and when isLoading
is false; use the ProjectCard props (escrow, tokenSale, escrowId, isLoading) to
gate the InvestDialog rendering and disable the CTA otherwise (also fix the same
guard where InvestDialog is rendered around lines 106-125).

Comment on lines +57 to +70
const filteredData = useMemo(() => {
return data.filter((item) => {
const escrow = escrowsById[item.escrowId];
if (search) {
const q = search.toLowerCase();
const title = (escrow?.title ?? "").toLowerCase();
const desc = (escrow?.description ?? "").toLowerCase();
if (!title.includes(q) && !desc.includes(q)) return false;
}
if (filter === "active") return escrow?.isActive === true;
if (filter === "fundraising") return !escrow?.isActive;
return true;
});
}, [search, filter, escrowsById]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Don't turn unresolved escrows into a real filter state.

!escrow?.isActive makes missing data look "fundraising", while the "active" branch hides every card until the query resolves. That drops the loading skeletons for one filter and misclassifies fetch misses for the other.

💡 One safe way to keep loading/error states out of the status filters
   const filteredData = useMemo(() => {
     return data.filter((item) => {
       const escrow = escrowsById[item.escrowId];
+      if (!escrow) return isLoading;
+
       if (search) {
         const q = search.toLowerCase();
         const title = (escrow?.title ?? "").toLowerCase();
         const desc = (escrow?.description ?? "").toLowerCase();
         if (!title.includes(q) && !desc.includes(q)) return false;
       }
-      if (filter === "active") return escrow?.isActive === true;
-      if (filter === "fundraising") return !escrow?.isActive;
+      if (filter === "active") return escrow.isActive === true;
+      if (filter === "fundraising") return escrow.isActive === false;
       return true;
     });
-  }, [search, filter, escrowsById]);
+  }, [search, filter, escrowsById, isLoading]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/features/transparency/ProjectList.tsx` around
lines 57 - 70, filteredData's status filtering treats missing escrows as
"fundraising" and hides unresolved items for "active"; update the useMemo filter
to explicitly handle undefined escrows: use escrowsById lookup (escrow) and if
escrow is null/undefined return true so unresolved items are not excluded by any
status filter, change the "active" case to require escrow?.isActive === true
(but allow unresolved to pass through) and change the "fundraising" case to
require escrow?.isActive === false (do NOT use !escrow?.isActive), referencing
the filteredData useMemo, escrowsById, search and filter variables to locate and
fix the logic.

Copy link
Contributor

@JoelVR17 JoelVR17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job!

@JoelVR17 JoelVR17 merged commit 87c2857 into Trustless-Work:develop Mar 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants