Skip to content

Details#26

Open
sunilkumawat-96 wants to merge 1 commit intoStabilityNexus:mainfrom
sunilkumawat-96:main
Open

Details#26
sunilkumawat-96 wants to merge 1 commit intoStabilityNexus:mainfrom
sunilkumawat-96:main

Conversation

@sunilkumawat-96
Copy link
Copy Markdown

@sunilkumawat-96 sunilkumawat-96 commented Dec 13, 2025

A Share button was added to the campaign details page.
Clicking it opens a modal that lets users share the campaign on Twitter/X, Telegram, and LinkedIn.
The share links use intent URLs with a pre-filled message and organization tag, making sharing quick and easy and improving campaign visibility.

Summary by CodeRabbit

  • New Features
    • Share button added to vault details header for easy sharing functionality.
    • New sections now display Proof-of-Funding Tokens, Funds Collected, Time Left, Creator information, and project URL.
    • Real-time countdown timer and token metrics now visible with improved data-driven UI.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 13, 2025

Walkthrough

The Details.tsx component was significantly refactored to implement a new data fetching architecture using getVaults via useReadContract, introduce ShareModal state management, and replace placeholder UI sections with conditional data-driven rendering that displays vault metrics, funding information, and creator details.

Changes

Cohort / File(s) Change Summary
Details Component Refactor
src/Details.tsx
Added ShareModal state and imports; refactored data fetching to use getVaults and gate subsequent queries on data availability; replaced large placeholder sections with conditional rendering blocks for Proof-of-Funding Tokens, Funds Collected, Time Left, Description, Creator, and Explore More; integrated Countdown component, Microlink display, and ShareModal with open/close handlers; added Share button to header; updated VaultActions to receive withdrawalAddress from vaultDetails.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Data fetching flow restructuring and query gating logic
  • Conditional rendering dependencies across multiple data sources (balance, symbol, vaultDetails)
  • ShareModal state management and component integration
  • New UI sections using vault metrics and formatted ether values

Poem

A rabbit hops through refactored code, 🐰
Details now gleam with data's load,
Share buttons prompt the vault to shine,
Placeholders fade, real metrics align,
Details bloom where stubs once lay!

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The pull request title 'Details' is overly vague and does not convey meaningful information about the changeset. While the PR adds multiple features to the Details component (Share button, ShareModal, data fetching improvements, new UI sections), the title fails to specify what actually changed. Use a more descriptive title that captures the main change, such as 'Add Share button and ShareModal to Details page' or 'Implement campaign sharing feature on Details page'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
Copy Markdown

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/Details.tsx (2)

34-62: totalSupply is being read from the wrong contract and enabled flags have logic errors.

totalSupply is read from address (vault) but should be read from vaultDetails?.participationToken to match where symbol is fetched. Since abi is an ERC20 ABI, both functions should operate on the same token contract. Additionally, the enabled: balanceOfVault !== undefined check (line 45) is always true—balanceOfVault is the hook object itself, not its value. Use balanceOfVault?.data?.value !== undefined or check if participationToken exists. The type casting is also incorrect: totalSupply returns uint256 (bigint in TypeScript), not string, which causes issues downstream where VaultCAT is converted with BigInt() at line 97.

-  const result = useReadContract({
+  const participationToken = vaultDetails?.participationToken as
+    | `0x${string}`
+    | undefined;
+
+  const result = useReadContract({
     abi: abi,
-    address: address,
+    address: participationToken,
     functionName: "totalSupply",
     chainId: citreaTestnet.id,
     query: {
-      enabled: balanceOfVault !== undefined,
+      enabled: Boolean(participationToken),
     },
   });

-  const VaultCAT = result?.data as string;
+  const VaultCAT = result?.data as bigint | undefined;

   const _symbol = useReadContract({
     abi: abi,
-    address: vaultDetails?.participationToken,
+    address: participationToken,
     functionName: "symbol",
     chainId: citreaTestnet.id,
     query: {
-      enabled: VaultCAT !== undefined,
+      enabled: Boolean(participationToken),
     },
   });

-  const symbol = _symbol?.data as string;
+  const symbol = _symbol?.data as string | undefined;

3-32: Guard contract reads on address presence (avoid undefined-address calls).
useParams() can yield address === undefined; several hooks pass it directly. Make query.enabled depend on Boolean(address) (and pass address only when defined) to avoid runtime/provider errors.

-  const balanceOfVault = useBalance({
-    address: address,
+  const balanceOfVault = useBalance({
+    address,
     chainId: citreaTestnet.id,
+    query: { enabled: Boolean(address) },
   });

  const response = useReadContract({
    abi: vaultabi,
-    address: address,
+    address,
    functionName: "getVaults",
    chainId: citreaTestnet.id,
    query: {
-      enabled: balanceOfVault?.data?.value !== undefined,
+      enabled: Boolean(address),
    },
  });
In the current wagmi version used by this repo, does `useReadContract` safely accept `address: undefined` when `query.enabled` is false, or should the `address` field be omitted entirely until enabled?
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b816112 and d55914f.

📒 Files selected for processing (1)
  • src/Details.tsx (3 hunks)
🔇 Additional comments (1)
src/Details.tsx (1)

158-165: ShareModal wiring looks clean; verify it includes a shareable URL.
State + open/close plumbing is straightforward; just ensure ShareModal shares a stable campaign link (e.g., window.location.href or sanitized projectURL) rather than title-only.

Comment on lines +66 to +155
{balanceOfVault.data && symbol && vaultDetails && (
<div>
{/* Title + Share */}
<div className="flex justify-between items-center pb-6">
<h1 className="text-2xl font-bold">
{vaultDetails.projectTitle}
</h1>
<button
onClick={() => setOpenShare(true)}
className="px-4 py-2 rounded-md bg-purple-600 hover:bg-purple-700 text-white"
>
Share
</button>
</div>

<div className="flex flex-row flex-wrap xl:flex-nowrap justify-around">
<div className="xl:w-1/2 w-full">
<Microlink
url={vaultDetails.projectURL}
size="large"
rounded="5"
contrast
/>
</div>

<div className="grid gap-4 xl:grid-cols-[40%_60%] py-3">
{/* Detailed Cards Section */}
<div>
<div className="mb-5 rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<div className="flex items-center mb-4">
<div className="mr-4 p-3 rounded-full text-2xl">📝</div>
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
</div>
<div className="h-4 bg-slate-800 rounded my-2 mx-2 w-36 animate-pulse"></div>
<div className="bg-slate-950 text-xs font-mono p-2 rounded break-all animate-pulse"></div>
</div>
<div className="xl:w-full w-full">
<div className="xl:ml-5 my-5 px-5 py-5 border border-slate-950 shadow-md">
<h1 className="text-slate-400">Proof-of-Funding Tokens</h1>
<p>
{formatEther(
BigInt(vaultDetails.participationTokenAmount) -
BigInt(VaultCAT),
)}{" "}
{symbol} remaining
</p>
</div>

<div className=" rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<div className="flex items-center mb-4">
<div className="mr-4 p-3 rounded-full text-2xl"> 👨🏻‍💼</div>
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
</div>
<div className="h-4 bg-slate-800 rounded my-2 mx-2 w-36 animate-pulse"></div>
</div>
<div className="xl:ml-5 my-5 px-5 py-5 border border-slate-950 shadow-md">
<h1 className="text-slate-400">Funds Collected</h1>
<p>
{formatEther(balanceOfVault.data.value)}{" "}
{balanceOfVault.data.symbol}
</p>
</div>
<div className="border border-slate-950 shadow-md">
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
<div className="flex">
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
</div>

<div className="xl:ml-5 my-5 px-5 py-5 rounded-lg shadow-md border border-slate-950">
<h3 className="text-slate-400">Time Left</h3>
<Countdown
targetTimestamp={Number(vaultDetails.timeStamp) * 1000}
/>
</div>
</div>
</div>
)}
{balanceOfVault.data && symbol && vaultDetails && (
<div>
<h1 className="text-2xl font-bold text-white pb-6 ">
{vaultDetails.projectTitle}
</h1>
<div className="flex flex-row flex-wrap xl:flex-nowrap justify-around">
<div className="xl:w-1/2 w-full ">
<Microlink
url={vaultDetails.projectURL}
size="large"
rounded="5"
contrast
/>
</div>
<div className="xl:w-full w-full">
<div className=" xl:ml-5 my-5 px-5 py-5 border border-slate-950 shadow-md">
<h1 className="text-slate-400">Proof-of-Funding Tokens</h1>
<p>
{formatEther(
BigInt(vaultDetails.participationTokenAmount) -
BigInt(VaultCAT),
)}{" "}
{symbol} Vouchers Remaining out of{" "}
{formatEther(
BigInt(vaultDetails.participationTokenAmount),
)}{" "}
{symbol} Vouchers
</p>
<div
className=" flex w-full h-2 rounded-full overflow-hidden bg-slate-950"
role="progressbar"
aria-valuenow={25}
aria-valuemin={0}
aria-valuemax={100}
>
<div
className="flex flex-col h-2 content-center overflow-hidden bg-purple-500 text-xs text-white text-center whitespace-nowrap transition duration-500 "
style={{
width: `${((Number(vaultDetails.participationTokenAmount) - Number(VaultCAT)) / Number(vaultDetails.participationTokenAmount)) * 100}%`,
}}
></div>
</div>
</div>
<div className=" xl:ml-5 my-5 px-5 py-5 border border-slate-950 shadow-md">
<h1 className="text-slate-400">Funds Collected</h1>
<p>
{formatEther(balanceOfVault?.data?.value as bigint)}{" "}
{balanceOfVault?.data?.symbol} Funds raised of{" "}
{formatEther(BigInt(vaultDetails.minFundingAmount))}{" "}
{balanceOfVault?.data?.symbol}
</p>
<div
className=" flex w-full h-2 rounded-full overflow-hidden bg-slate-950"
role="progressbar"
aria-valuenow={25}
aria-valuemin={0}
aria-valuemax={100}
>
<div
className="flex flex-col h-2 content-center overflow-hidden bg-purple-500 text-xs text-white text-center whitespace-nowrap transition duration-500 "
style={{
width: `${(Number(balanceOfVault?.data?.value) / Number(vaultDetails.minFundingAmount)) * 100}%`,
}}
></div>
</div>
</div>
<div className="xl:ml-5 my-5 px-5 py-5 rounded-lg shadow-md border border-slate-950">
<div>
<h3 className="text-slate-400">Time Left</h3>

<Countdown
targetTimestamp={Number(vaultDetails.timeStamp) * 1000}
/>
</div>
</div>
<div className="grid gap-4 xl:grid-cols-[40%_60%] py-3">
<div>
<div className="mb-4 rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<h3 className="text-lg font-semibold mb-2">Description</h3>
<p className="text-sm text-slate-400">
{vaultDetails.projectDescription}
</p>
</div>
</div>

<div className="grid gap-4 xl:grid-cols-[40%_60%] py-3">
<div className="">
<div className="mb-4 rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<div className="flex items-center mb-4">
<div className="mr-4 p-3 rounded-full text-2xl">📝</div>
<h3 className="text-lg font-semibold">Description</h3>
</div>
<p className="text-sm text-slate-400">
{vaultDetails.projectDescription}
</p>
</div>
<div className=" rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<div className="flex items-center mb-4">
<div className="mr-4 p-3 rounded-full text-2xl">👨🏻‍💼</div>
<h3 className="text-lg font-semibold">Creator</h3>
</div>
<p className="text-sm text-gray-500 mb-2">
Wallet Address:
</p>
<div className="bg-slate-950 text-xs font-mono p-2 rounded break-all">
{vaultDetails.withdrawlAddress}
</div>
<div className="rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<h3 className="text-lg font-semibold mb-2">Creator</h3>
<div className="bg-slate-950 text-xs font-mono p-2 rounded break-all">
{vaultDetails.withdrawlAddress}
</div>
</div>
<div>
{balanceOfVault.data && symbol && vaultDetails && (
<VaultActions
withdrawalAddress={vaultDetails?.withdrawlAddress}
/>
)}
</div>
</div>

<div className="flex place-content-center py-3">
<a
href={vaultDetails.projectURL}
target="_blank"
rel="noopener noreferrer"
className="flex min-w-60 overflow-hidden items-center font-medium focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 bg-slate-950 text-white shadow hover:bg-black/90 px-4 py-2 max-w-52 whitespace-pre md:flex group relative w-full justify-center gap-2 rounded-md transition-all duration-300 ease-out border-2 border-purple-600/70 hover:border-purple-600 mt-3"
>
<span className="absolute right-0 h-32 w-8 translate-x-12 rotate-12 bg-white opacity-20 transition-all duration-1000 ease-out group-hover:-translate-x-40"></span>

<span className="text-white">Explore More about Project</span>
</a>
<div>
<VaultActions
withdrawalAddress={vaultDetails.withdrawlAddress}
/>
</div>
</div>
)}
</div>

<div className="flex place-content-center py-3">
<a
href={vaultDetails.projectURL}
target="_blank"
rel="noopener noreferrer"
className="bg-slate-950 border border-purple-600 px-4 py-2 rounded-md hover:bg-black"
>
Explore More about Project
</a>
</div>
</div>
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Sanitize projectURL before using it in href/Microlink (protocol allowlist).
Since projectURL is user-controlled/on-chain, using it directly can enable javascript: links and other undesirable schemes. Allow only http:/https: and render a safe fallback otherwise.

+            const safeProjectUrl = (() => {
+              try {
+                const u = new URL(vaultDetails.projectURL);
+                return u.protocol === "http:" || u.protocol === "https:"
+                  ? u.toString()
+                  : undefined;
+              } catch {
+                return undefined;
+              }
+            })();

             <div className="flex flex-row flex-wrap xl:flex-nowrap justify-around">
               <div className="xl:w-1/2 w-full">
-                <Microlink
-                  url={vaultDetails.projectURL}
-                  size="large"
-                  rounded="5"
-                  contrast
-                />
+                {safeProjectUrl && (
+                  <Microlink url={safeProjectUrl} size="large" rounded="5" contrast />
+                )}
               </div>
...
               <a
-                href={vaultDetails.projectURL}
+                href={safeProjectUrl}
                 target="_blank"
                 rel="noopener noreferrer"
+                aria-disabled={!safeProjectUrl}
                 className="bg-slate-950 border border-purple-600 px-4 py-2 rounded-md hover:bg-black"
               >
                 Explore More about Project
               </a>
📝 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
{balanceOfVault.data && symbol && vaultDetails && (
<div>
{/* Title + Share */}
<div className="flex justify-between items-center pb-6">
<h1 className="text-2xl font-bold">
{vaultDetails.projectTitle}
</h1>
<button
onClick={() => setOpenShare(true)}
className="px-4 py-2 rounded-md bg-purple-600 hover:bg-purple-700 text-white"
>
Share
</button>
</div>
<div className="flex flex-row flex-wrap xl:flex-nowrap justify-around">
<div className="xl:w-1/2 w-full">
<Microlink
url={vaultDetails.projectURL}
size="large"
rounded="5"
contrast
/>
</div>
<div className="grid gap-4 xl:grid-cols-[40%_60%] py-3">
{/* Detailed Cards Section */}
<div>
<div className="mb-5 rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<div className="flex items-center mb-4">
<div className="mr-4 p-3 rounded-full text-2xl">📝</div>
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
</div>
<div className="h-4 bg-slate-800 rounded my-2 mx-2 w-36 animate-pulse"></div>
<div className="bg-slate-950 text-xs font-mono p-2 rounded break-all animate-pulse"></div>
</div>
<div className="xl:w-full w-full">
<div className="xl:ml-5 my-5 px-5 py-5 border border-slate-950 shadow-md">
<h1 className="text-slate-400">Proof-of-Funding Tokens</h1>
<p>
{formatEther(
BigInt(vaultDetails.participationTokenAmount) -
BigInt(VaultCAT),
)}{" "}
{symbol} remaining
</p>
</div>
<div className=" rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<div className="flex items-center mb-4">
<div className="mr-4 p-3 rounded-full text-2xl"> 👨🏻‍💼</div>
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
</div>
<div className="h-4 bg-slate-800 rounded my-2 mx-2 w-36 animate-pulse"></div>
</div>
<div className="xl:ml-5 my-5 px-5 py-5 border border-slate-950 shadow-md">
<h1 className="text-slate-400">Funds Collected</h1>
<p>
{formatEther(balanceOfVault.data.value)}{" "}
{balanceOfVault.data.symbol}
</p>
</div>
<div className="border border-slate-950 shadow-md">
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
<div className="flex">
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
<div className="h-6 bg-slate-800 rounded my-2 mx-2 w-28 animate-pulse"></div>
</div>
<div className="xl:ml-5 my-5 px-5 py-5 rounded-lg shadow-md border border-slate-950">
<h3 className="text-slate-400">Time Left</h3>
<Countdown
targetTimestamp={Number(vaultDetails.timeStamp) * 1000}
/>
</div>
</div>
</div>
)}
{balanceOfVault.data && symbol && vaultDetails && (
<div>
<h1 className="text-2xl font-bold text-white pb-6 ">
{vaultDetails.projectTitle}
</h1>
<div className="flex flex-row flex-wrap xl:flex-nowrap justify-around">
<div className="xl:w-1/2 w-full ">
<Microlink
url={vaultDetails.projectURL}
size="large"
rounded="5"
contrast
/>
</div>
<div className="xl:w-full w-full">
<div className=" xl:ml-5 my-5 px-5 py-5 border border-slate-950 shadow-md">
<h1 className="text-slate-400">Proof-of-Funding Tokens</h1>
<p>
{formatEther(
BigInt(vaultDetails.participationTokenAmount) -
BigInt(VaultCAT),
)}{" "}
{symbol} Vouchers Remaining out of{" "}
{formatEther(
BigInt(vaultDetails.participationTokenAmount),
)}{" "}
{symbol} Vouchers
</p>
<div
className=" flex w-full h-2 rounded-full overflow-hidden bg-slate-950"
role="progressbar"
aria-valuenow={25}
aria-valuemin={0}
aria-valuemax={100}
>
<div
className="flex flex-col h-2 content-center overflow-hidden bg-purple-500 text-xs text-white text-center whitespace-nowrap transition duration-500 "
style={{
width: `${((Number(vaultDetails.participationTokenAmount) - Number(VaultCAT)) / Number(vaultDetails.participationTokenAmount)) * 100}%`,
}}
></div>
</div>
</div>
<div className=" xl:ml-5 my-5 px-5 py-5 border border-slate-950 shadow-md">
<h1 className="text-slate-400">Funds Collected</h1>
<p>
{formatEther(balanceOfVault?.data?.value as bigint)}{" "}
{balanceOfVault?.data?.symbol} Funds raised of{" "}
{formatEther(BigInt(vaultDetails.minFundingAmount))}{" "}
{balanceOfVault?.data?.symbol}
</p>
<div
className=" flex w-full h-2 rounded-full overflow-hidden bg-slate-950"
role="progressbar"
aria-valuenow={25}
aria-valuemin={0}
aria-valuemax={100}
>
<div
className="flex flex-col h-2 content-center overflow-hidden bg-purple-500 text-xs text-white text-center whitespace-nowrap transition duration-500 "
style={{
width: `${(Number(balanceOfVault?.data?.value) / Number(vaultDetails.minFundingAmount)) * 100}%`,
}}
></div>
</div>
</div>
<div className="xl:ml-5 my-5 px-5 py-5 rounded-lg shadow-md border border-slate-950">
<div>
<h3 className="text-slate-400">Time Left</h3>
<Countdown
targetTimestamp={Number(vaultDetails.timeStamp) * 1000}
/>
</div>
</div>
<div className="grid gap-4 xl:grid-cols-[40%_60%] py-3">
<div>
<div className="mb-4 rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<h3 className="text-lg font-semibold mb-2">Description</h3>
<p className="text-sm text-slate-400">
{vaultDetails.projectDescription}
</p>
</div>
</div>
<div className="grid gap-4 xl:grid-cols-[40%_60%] py-3">
<div className="">
<div className="mb-4 rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<div className="flex items-center mb-4">
<div className="mr-4 p-3 rounded-full text-2xl">📝</div>
<h3 className="text-lg font-semibold">Description</h3>
</div>
<p className="text-sm text-slate-400">
{vaultDetails.projectDescription}
</p>
</div>
<div className=" rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<div className="flex items-center mb-4">
<div className="mr-4 p-3 rounded-full text-2xl">👨🏻‍💼</div>
<h3 className="text-lg font-semibold">Creator</h3>
</div>
<p className="text-sm text-gray-500 mb-2">
Wallet Address:
</p>
<div className="bg-slate-950 text-xs font-mono p-2 rounded break-all">
{vaultDetails.withdrawlAddress}
</div>
<div className="rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<h3 className="text-lg font-semibold mb-2">Creator</h3>
<div className="bg-slate-950 text-xs font-mono p-2 rounded break-all">
{vaultDetails.withdrawlAddress}
</div>
</div>
<div>
{balanceOfVault.data && symbol && vaultDetails && (
<VaultActions
withdrawalAddress={vaultDetails?.withdrawlAddress}
/>
)}
</div>
</div>
<div className="flex place-content-center py-3">
<a
href={vaultDetails.projectURL}
target="_blank"
rel="noopener noreferrer"
className="flex min-w-60 overflow-hidden items-center font-medium focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 bg-slate-950 text-white shadow hover:bg-black/90 px-4 py-2 max-w-52 whitespace-pre md:flex group relative w-full justify-center gap-2 rounded-md transition-all duration-300 ease-out border-2 border-purple-600/70 hover:border-purple-600 mt-3"
>
<span className="absolute right-0 h-32 w-8 translate-x-12 rotate-12 bg-white opacity-20 transition-all duration-1000 ease-out group-hover:-translate-x-40"></span>
<span className="text-white">Explore More about Project</span>
</a>
<div>
<VaultActions
withdrawalAddress={vaultDetails.withdrawlAddress}
/>
</div>
</div>
)}
</div>
<div className="flex place-content-center py-3">
<a
href={vaultDetails.projectURL}
target="_blank"
rel="noopener noreferrer"
className="bg-slate-950 border border-purple-600 px-4 py-2 rounded-md hover:bg-black"
>
Explore More about Project
</a>
</div>
</div>
)}
{balanceOfVault.data && symbol && vaultDetails && (
<div>
{/* Title + Share */}
<div className="flex justify-between items-center pb-6">
<h1 className="text-2xl font-bold">
{vaultDetails.projectTitle}
</h1>
<button
onClick={() => setOpenShare(true)}
className="px-4 py-2 rounded-md bg-purple-600 hover:bg-purple-700 text-white"
>
Share
</button>
</div>
<div className="flex flex-row flex-wrap xl:flex-nowrap justify-around">
<div className="xl:w-1/2 w-full">
{(() => {
const safeProjectUrl = (() => {
try {
const u = new URL(vaultDetails.projectURL);
return u.protocol === "http:" || u.protocol === "https:"
? u.toString()
: undefined;
} catch {
return undefined;
}
})();
return safeProjectUrl && (
<Microlink url={safeProjectUrl} size="large" rounded="5" contrast />
);
})()}
</div>
<div className="xl:w-full w-full">
<div className="xl:ml-5 my-5 px-5 py-5 border border-slate-950 shadow-md">
<h1 className="text-slate-400">Proof-of-Funding Tokens</h1>
<p>
{formatEther(
BigInt(vaultDetails.participationTokenAmount) -
BigInt(VaultCAT),
)}{" "}
{symbol} remaining
</p>
</div>
<div className="xl:ml-5 my-5 px-5 py-5 border border-slate-950 shadow-md">
<h1 className="text-slate-400">Funds Collected</h1>
<p>
{formatEther(balanceOfVault.data.value)}{" "}
{balanceOfVault.data.symbol}
</p>
</div>
<div className="xl:ml-5 my-5 px-5 py-5 rounded-lg shadow-md border border-slate-950">
<h3 className="text-slate-400">Time Left</h3>
<Countdown
targetTimestamp={Number(vaultDetails.timeStamp) * 1000}
/>
</div>
</div>
</div>
<div className="grid gap-4 xl:grid-cols-[40%_60%] py-3">
<div>
<div className="mb-4 rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<h3 className="text-lg font-semibold mb-2">Description</h3>
<p className="text-sm text-slate-400">
{vaultDetails.projectDescription}
</p>
</div>
<div className="rounded-lg shadow-md border p-6 bg-slate-900 border-slate-950">
<h3 className="text-lg font-semibold mb-2">Creator</h3>
<div className="bg-slate-950 text-xs font-mono p-2 rounded break-all">
{vaultDetails.withdrawlAddress}
</div>
</div>
</div>
<div>
<VaultActions
withdrawalAddress={vaultDetails.withdrawlAddress}
/>
</div>
</div>
<div className="flex place-content-center py-3">
{(() => {
const safeProjectUrl = (() => {
try {
const u = new URL(vaultDetails.projectURL);
return u.protocol === "http:" || u.protocol === "https:"
? u.toString()
: undefined;
} catch {
return undefined;
}
})();
return (
<a
href={safeProjectUrl}
target="_blank"
rel="noopener noreferrer"
aria-disabled={!safeProjectUrl}
className="bg-slate-950 border border-purple-600 px-4 py-2 rounded-md hover:bg-black"
>
Explore More about Project
</a>
);
})()}
</div>
</div>
)}
🤖 Prompt for AI Agents
In src/Details.tsx around lines 66 to 155, the user-controlled
vaultDetails.projectURL is used directly in both Microlink and an anchor href;
sanitize it by parsing the URL and only allowing http and https schemes. If
parsing fails or the scheme is not allowed, replace projectURL with a safe
fallback (e.g., an empty string or a validated placeholder URL) and disable the
external link/action (do not render Microlink or set anchor href to the fallback
and remove target="_blank"), or show a non-clickable message; apply the same
sanitized value to both Microlink and the href, and ensure you do not pass
unsanitized projectURL to the DOM.

Comment on lines +91 to +101
<div className="xl:w-full w-full">
<div className="xl:ml-5 my-5 px-5 py-5 border border-slate-950 shadow-md">
<h1 className="text-slate-400">Proof-of-Funding Tokens</h1>
<p>
{formatEther(
BigInt(vaultDetails.participationTokenAmount) -
BigInt(VaultCAT),
)}{" "}
{symbol} remaining
</p>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clamp “remaining tokens” to avoid negative values.
If VaultCAT > participationTokenAmount, you’ll show a negative remaining count. Clamp to 0 for a stable UI.

-                    {formatEther(
-                      BigInt(vaultDetails.participationTokenAmount) -
-                        BigInt(VaultCAT),
-                    )}{" "}
+                    {formatEther(
+                      (BigInt(vaultDetails.participationTokenAmount) -
+                        BigInt(VaultCAT ?? 0n)) > 0n
+                        ? BigInt(vaultDetails.participationTokenAmount) - BigInt(VaultCAT ?? 0n)
+                        : 0n,
+                    )}{" "}
                     {symbol} remaining
📝 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
<div className="xl:w-full w-full">
<div className="xl:ml-5 my-5 px-5 py-5 border border-slate-950 shadow-md">
<h1 className="text-slate-400">Proof-of-Funding Tokens</h1>
<p>
{formatEther(
BigInt(vaultDetails.participationTokenAmount) -
BigInt(VaultCAT),
)}{" "}
{symbol} remaining
</p>
</div>
<div className="xl:w-full w-full">
<div className="xl:ml-5 my-5 px-5 py-5 border border-slate-950 shadow-md">
<h1 className="text-slate-400">Proof-of-Funding Tokens</h1>
<p>
{formatEther(
(BigInt(vaultDetails.participationTokenAmount) -
BigInt(VaultCAT ?? 0n)) > 0n
? BigInt(vaultDetails.participationTokenAmount) - BigInt(VaultCAT ?? 0n)
: 0n,
)}{" "}
{symbol} remaining
</p>
</div>
</div>
🤖 Prompt for AI Agents
In src/Details.tsx around lines 91 to 101, the UI can render a negative
"remaining" token count when VaultCAT > participationTokenAmount; change the
calculation to clamp the result to zero before passing to formatEther (compute
the difference as BigInt, take the maximum with 0n to prevent negatives, then
convert to the type expected by formatEther), and render that clamped value so
the component never displays a negative remaining balance.

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.

2 participants