From 9d79a78fa138059e5f53ed3ae0ad4ca89f41a0e3 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Jun 2026 22:24:16 +0100 Subject: [PATCH] issue --- frontend/src/components/ProjectCard.tsx | 111 ++++++++++++------ .../components/__tests__/ProjectCard.test.tsx | 38 ++++++ .../components/dashboard/DashboardView.tsx | 17 +-- .../src/components/projects/ProjectsList.tsx | 18 +-- 4 files changed, 126 insertions(+), 58 deletions(-) create mode 100644 frontend/src/components/__tests__/ProjectCard.test.tsx diff --git a/frontend/src/components/ProjectCard.tsx b/frontend/src/components/ProjectCard.tsx index be014ea2..2a372d3f 100644 --- a/frontend/src/components/ProjectCard.tsx +++ b/frontend/src/components/ProjectCard.tsx @@ -1,45 +1,88 @@ "use client"; import { clsx } from "clsx"; +import { sanitizeText } from "@/lib/security"; +import type { SplitProject } from "@/lib/stellar"; export interface ProjectCardProps { - id: string; - name: string; - status: "active" | "locked" | "inactive"; - balance: string; - collaboratorCount: number; - onDistribute?: (id: string) => void; + project: SplitProject; + userEarnings?: string; + onDistribute?: () => void; + onSelect?: () => void; + isSelected?: boolean; } -const statusClasses: Record = { - active: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400", - locked: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400", - inactive: "bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400", -}; - -export function ProjectCard({ id, name, status, balance, collaboratorCount, onDistribute }: ProjectCardProps) { - return ( -
-
-

{name}

- - {status} - -
+export function ProjectCard({ project, userEarnings, onDistribute, onSelect, isSelected }: ProjectCardProps) { + const Compact = Boolean(userEarnings); -
- Balance: {balance} - {collaboratorCount} collaborator{collaboratorCount !== 1 ? "s" : ""} -
+ const outerClass = Compact + ? "bg-white/5 rounded-2xl p-5 border border-white/5 flex justify-between items-center" + : "glass-card rounded-[2.5rem] p-8 text-left hover:bg-white/5 transition-all"; + + const Content = ( + <> +
+
+

+ {sanitizeText(project.title)} +

+ + {sanitizeText(project.projectType)} + + {project.locked && ( + + Locked + + )} +
+ + {!Compact &&

{project.projectId}

} + +
+
+

+ {Number(project.balance).toLocaleString()} +

+ {Compact &&

{userEarnings ? `+${Number(userEarnings).toLocaleString()}` : ""}

} +
- {onDistribute && ( - - )} -
+
+

{Compact ? "Earnings" : "Available"}

+

{project.collaborators.length} collaborator{project.collaborators.length !== 1 ? "s" : ""}

+
+
+ + {!Compact && onDistribute && ( +
+ +
+ )} +
+ ); + + if (onSelect) { + return ( + + ); + } + + return
{Content}
; } diff --git a/frontend/src/components/__tests__/ProjectCard.test.tsx b/frontend/src/components/__tests__/ProjectCard.test.tsx new file mode 100644 index 00000000..0f54ffc3 --- /dev/null +++ b/frontend/src/components/__tests__/ProjectCard.test.tsx @@ -0,0 +1,38 @@ +/* @vitest-environment jsdom */ + +import { render, screen } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; + +import { ProjectCard } from "../ProjectCard"; +import type { SplitProject } from "@/lib/stellar"; + +const baseProject = (overrides: Partial = {}): SplitProject => ({ + projectId: "P123", + title: "Test Project", + projectType: "App", + token: "", + owner: "GABC", + collaborators: [{ address: "G1", alias: "Alice", basisPoints: 100 }], + locked: false, + totalDistributed: "0", + distributionRound: 1, + balance: "1500", + ...overrides, +}); + +describe("ProjectCard", () => { + it("renders unlocked project without Locked badge", () => { + render(); + + expect(screen.getByText(/Test Project/)).toBeTruthy(); + expect(screen.queryByText(/Locked/)).toBeNull(); + expect(screen.getByText(/Available|Earnings/)).toBeTruthy(); + }); + + it("renders locked project with Locked badge", () => { + render(); + + expect(screen.getByText(/Locked/)).toBeTruthy(); + expect(screen.getByText(/Test Project/)).toBeTruthy(); + }); +}); diff --git a/frontend/src/components/dashboard/DashboardView.tsx b/frontend/src/components/dashboard/DashboardView.tsx index b42d07f3..f3b2031f 100644 --- a/frontend/src/components/dashboard/DashboardView.tsx +++ b/frontend/src/components/dashboard/DashboardView.tsx @@ -10,6 +10,7 @@ import type { } from "@/lib/api"; import type { WalletState } from "@/lib/wallet"; import { SummaryCardSkeleton } from "../Skeleton"; +import { ProjectCard } from "../ProjectCard"; export interface AllowlistActionResult { action: "allow" | "disallow"; @@ -561,17 +562,11 @@ export function DashboardView({ {dashboardData .filter((p) => p.collaborators.some((c) => c.address === wallet.address)) .map((p) => ( -
-
-

{sanitizeText(p.title)}

-

- {(p.collaborators.find((c) => c.address === wallet.address)?.basisPoints ?? 0) / 100}% Share -

-
-

- +{Number(userEarnings[p.projectId] || 0).toLocaleString()} -

-
+ ))} diff --git a/frontend/src/components/projects/ProjectsList.tsx b/frontend/src/components/projects/ProjectsList.tsx index 3122b4ad..6e3b4c46 100644 --- a/frontend/src/components/projects/ProjectsList.tsx +++ b/frontend/src/components/projects/ProjectsList.tsx @@ -6,6 +6,7 @@ import type { SplitProject } from "@/lib/stellar"; import type { ProjectHistoryItem, AdminStatusState } from "@/lib/api"; import type { WalletState } from "@/lib/wallet"; import { DashboardGridSkeleton, ListSkeleton } from "../Skeleton"; +import { ProjectCard } from "../ProjectCard"; import { TransactionReceiptView, type TransactionReceipt } from "../TransactionReceiptView"; interface ProjectsListProps { @@ -106,24 +107,15 @@ export function ProjectsList({ ) : projectsList.length > 0 ? (
{projectsList.map((p) => ( - + /> ))}
) : (