= ({
disabled={!stripe || isProcessing}
className="w-full py-3 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg shadow-md transition-all"
>
- {isProcessing ? 'Processing...' : `Pay $${amount.toLocaleString()}`}
+ {isProcessing ? 'Processing...' : `Pay $${Number(amount).toLocaleString()}`}
Funds will be held securely until admin approval.
diff --git a/src/components/DealReceipt.tsx b/src/components/DealReceipt.tsx
new file mode 100644
index 000000000..506e59efa
--- /dev/null
+++ b/src/components/DealReceipt.tsx
@@ -0,0 +1,425 @@
+import React, { useState, useEffect, useRef } from "react";
+import axios from "axios";
+import { toast } from "react-hot-toast";
+import { X, Download, Printer, CheckCircle2 } from "lucide-react";
+import { Button } from "./ui/Button";
+
+const URL = import.meta.env.VITE_BACKEND_URL;
+
+interface DealReceiptProps {
+ dealId: string;
+ onClose: () => void;
+}
+
+export const DealReceipt: React.FC = ({ dealId, onClose }) => {
+ const [loading, setLoading] = useState(true);
+ const [transactions, setTransactions] = useState([]);
+ const [selectedTransaction, setSelectedTransaction] = useState(null);
+ const [deal, setDeal] = useState(null);
+ const receiptId = "deal-receipt";
+ const receiptRef = useRef(null);
+
+ useEffect(() => {
+ fetchTransactionDetails();
+ }, [dealId]);
+
+ const fetchTransactionDetails = async () => {
+ try {
+ const res = await axios.get(`${URL}/deal/get-transaction/${dealId}`);
+ setTransactions(res.data.transactions || []);
+ setSelectedTransaction(res.data.transactions?.[0] || null); // Default to first (original investment)
+ setDeal(res.data.deal);
+ } catch (error) {
+ console.error("Error fetching transaction:", error);
+ toast.error("Failed to load receipt");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handlePrint = () => {
+ if (!receiptRef.current) {
+ toast.error("Receipt not ready to print");
+ return;
+ }
+
+ const printContents = receiptRef.current.outerHTML;
+ const styles = Array.from(
+ document.querySelectorAll('link[rel="stylesheet"], style')
+ )
+ .map((el) => el.outerHTML)
+ .join("");
+
+ const printWindow = window.open("", "_blank", "noopener,noreferrer");
+
+ if (!printWindow) {
+ toast.error("Popup blocked. Please allow popups to print.");
+ return;
+ }
+
+ printWindow.document.write(`
+
+
+ Receipt
+ ${styles}
+
+
+
+ ${printContents}
+
+
+ `);
+ printWindow.document.close();
+ printWindow.focus();
+ printWindow.print();
+ printWindow.close();
+ };
+
+ const handleDownload = async () => {
+ try {
+ // Using html2canvas and jsPDF for download functionality
+ const html2canvas = (await import("html2canvas")).default;
+ const jsPDF = (await import("jspdf")).default;
+
+ if (!receiptRef.current) {
+ toast.error("Receipt not ready to download");
+ return;
+ }
+
+ const canvas = await html2canvas(receiptRef.current, {
+ scale: 2,
+ useCORS: true,
+ logging: false,
+ });
+
+ const imgData = canvas.toDataURL("image/png");
+ const pdf = new jsPDF({
+ orientation: "landscape",
+ unit: "mm",
+ format: "a4",
+ });
+
+ const pdfWidth = pdf.internal.pageSize.getWidth();
+ const pdfHeight = pdf.internal.pageSize.getHeight();
+ const imgWidth = canvas.width;
+ const imgHeight = canvas.height;
+ const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight);
+ const imgX = (pdfWidth - imgWidth * ratio) / 2;
+ const imgY = 10;
+
+ pdf.addImage(
+ imgData,
+ "PNG",
+ imgX,
+ imgY,
+ imgWidth * ratio,
+ imgHeight * ratio
+ );
+ pdf.save(`Investment-Receipt-${selectedTransaction?.paymentIntentId || dealId}.pdf`);
+ toast.success("Receipt downloaded successfully!");
+ } catch (error) {
+ console.error("Error downloading receipt:", error);
+ toast.error("Failed to download receipt");
+ }
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (!selectedTransaction || !deal || transactions.length === 0) {
+ return (
+
+
+
Receipt not found
+
+ {!deal && "Deal information is missing."}
+ {deal && transactions.length === 0 && "No transaction records found for this deal."}
+ {deal && transactions.length > 0 && !selectedTransaction && "Unable to load transaction details."}
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ {/* Header Actions */}
+
+
+
Investment Receipt
+ {transactions.length > 1 && (
+
+ )}
+
+
+
+
+
+
+
+ {/* Receipt Content */}
+
+ {/* Logo */}
+
+
+

+
+
+
+ {/* Header with Status Badge */}
+
+
Investment Receipt
+
+
+
+ {selectedTransaction.status === 'funds_released' ? 'Payment Released' : 'Payment Confirmed'}
+
+
+
+
+ {/* Company/Platform Info */}
+
+
Trust Bridge Investment Platform
+
Connecting Investors with Innovative Startups
+
+
+ {/* Transaction Details Grid */}
+
+ {/* Left Column */}
+
+
+
INVESTOR DETAILS
+
+
{selectedTransaction.investorName}
+
{selectedTransaction.investorId?.email}
+
+
+
+
+
ENTREPRENEUR DETAILS
+
+
{selectedTransaction.entrepreneurName}
+
{selectedTransaction.entrepreneurId?.startupName}
+
{selectedTransaction.entrepreneurId?.email}
+
+
+
+
+ {/* Right Column */}
+
+
+
TRANSACTION INFO
+
+
+ Receipt No:
+ {selectedTransaction._id?.slice(-8).toUpperCase()}
+
+
+ Payment ID:
+ {selectedTransaction.paymentIntentId?.slice(0, 20)}...
+
+
+ Date:
+ {new Date(selectedTransaction.createdAt).toLocaleDateString()}
+
+
+ Time:
+ {new Date(selectedTransaction.createdAt).toLocaleTimeString()}
+
+
+
+
+
+
DEAL TERMS
+
+
+ Investment Type:
+ {deal.investmentType}
+
+
+ Equity Offered:
+ {deal.equityOffered}%
+
+
+ Valuation:
+ ${deal.postMoneyValuation?.toLocaleString()}
+
+
+
+
+
+
+ {/* Amount Breakdown */}
+
+
PAYMENT BREAKDOWN
+
+
+ Investment Amount
+ ${selectedTransaction.amount?.toLocaleString()}
+
+ {selectedTransaction.stripeFee && (
+
+ Processing Fee
+ -${selectedTransaction.stripeFee?.toFixed(2)}
+
+ )}
+ {selectedTransaction.platformCommission > 0 && (
+
+ Platform Commission
+ -${selectedTransaction.platformCommission?.toFixed(2)}
+
+ )}
+
+
+ Net Amount to Entrepreneur
+
+ ${selectedTransaction.netAmount?.toLocaleString()}
+
+
+
+
+
+
+ {/* Additional Info */}
+ {selectedTransaction.isAdditionalInvestment && (
+
+
+ ⚡ This is an additional investment to the existing deal.
+
+
+ )}
+
+ {/* Status Timeline */}
+
+
PAYMENT STATUS
+
+
+
+
+
Payment Received
+
{new Date(selectedTransaction.createdAt).toLocaleString()}
+
+
+ {selectedTransaction.adminActionDate && (
+
+
+
+
Funds Released to Entrepreneur
+
{new Date(selectedTransaction.adminActionDate).toLocaleString()}
+
+
+ )}
+ {selectedTransaction.status === 'paid' && !selectedTransaction.adminActionDate && (
+
+
+
+
Awaiting Admin Approval
+
Funds will be released soon
+
+
+ )}
+
+
+
+ {/* Footer */}
+
+
This is an official receipt generated by TrustBridge Investment Platform
+
For any queries, please contact aitrustbridge@gmail.com
+
Transaction ID: {selectedTransaction.paymentIntentId}
+
Generated on {new Date().toLocaleString()}
+
+
+
+
+ {/* Bottom Actions */}
+
+
+
+
+
+ {/* Print Styles */}
+
+
+ );
+};
diff --git a/src/components/camp/CampForm.tsx b/src/components/camp/CampForm.tsx
index 41d372ef4..579bd3980 100644
--- a/src/components/camp/CampForm.tsx
+++ b/src/components/camp/CampForm.tsx
@@ -30,6 +30,8 @@ const CampForm: React.FC = ({ onSuccess, initialData }) => {
const [videoPreview, setVideoPreview] = useState(null);
const [loading, setLoading] = useState(false);
const fileInputRef = useRef(null);
+ const [imageUrlInput, setImageUrlInput] = useState("");
+ const [isUrlModalOpen, setIsUrlModalOpen] = useState(false);
useEffect(() => {
if (initialData) {
@@ -140,6 +142,26 @@ const CampForm: React.FC = ({ onSuccess, initialData }) => {
}
};
+ const handleUrlUpload = () => {
+ if (!imageUrlInput.trim()) {
+ toast.error("Please enter a valid image URL");
+ return;
+ }
+
+ const totalImages = existingImages.length + images.length;
+ if (totalImages >= 3) {
+ toast.error("You can only have up to 3 images");
+ return;
+ }
+
+ // Add URL to existing images (will be sent as existingImages in form data)
+ setExistingImages(prev => [...prev, imageUrlInput]);
+ setPreviewUrls(prev => [...prev, imageUrlInput]);
+ toast.success("Image URL added successfully");
+ setImageUrlInput("");
+ setIsUrlModalOpen(false);
+ };
+
const validateForm = () => {
const { title, description, goalAmount, startDate, endDate, category, isLifetime } =
formData;
@@ -398,9 +420,20 @@ const CampForm: React.FC = ({ onSuccess, initialData }) => {
{/* Images Section */}
-
+
+
+ {(existingImages.length + images.length) < 3 && (
+
+ )}
+
{previewUrls.map((url, i) => (
@@ -499,6 +532,93 @@ const CampForm: React.FC = ({ onSuccess, initialData }) => {
+
+ {/* URL Upload Modal */}
+ {isUrlModalOpen && (
+
{
+ if (e.target === e.currentTarget) {
+ setIsUrlModalOpen(false);
+ setImageUrlInput("");
+ }
+ }}
+ >
+
+
+
+
+ Add Image URL
+
+
+
+
+
+
+
+
setImageUrlInput(e.target.value)}
+ placeholder="https://example.com/image.jpg"
+ className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
+ />
+
+ Enter a direct link to an image
+
+
+
+ {imageUrlInput && (
+
+
Preview:
+
+

{
+ (e.target as HTMLImageElement).src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Crect fill='%23f3f4f6' width='100' height='100'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='14' fill='%239ca3af' text-anchor='middle' dy='.3em'%3EInvalid URL%3C/text%3E%3C/svg%3E";
+ }}
+ />
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ )}
);
};
diff --git a/src/components/layout/DashboardLayout.tsx b/src/components/layout/DashboardLayout.tsx
index d1851a0a9..cd462d314 100644
--- a/src/components/layout/DashboardLayout.tsx
+++ b/src/components/layout/DashboardLayout.tsx
@@ -83,8 +83,8 @@ export const DashboardLayout: React.FC = () => {
-
-
+
+
diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx
index 6d888bfeb..3712a01f4 100644
--- a/src/components/layout/Navbar.tsx
+++ b/src/components/layout/Navbar.tsx
@@ -113,7 +113,7 @@ export const Navbar: React.FC = () => {
}
return (
-