feat: add payment tracking and manual payment submission to investment details and configure standalone deployment mode

This commit is contained in:
sazzadulalambd
2026-05-16 10:20:12 +06:00
parent 5e59909e8e
commit d8e82cef19
5 changed files with 159 additions and 45 deletions

3
.gitignore vendored
View File

@@ -50,3 +50,6 @@ next-env.d.ts
**/docs
**/.docs
**/deploy.zip
**/deploy

20
deploy.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Build the project
echo "Building project..."
npm run build
# Create a deployment folder
echo "Preparing deployment files..."
mkdir -p deploy
cp -r .next deploy/
cp -r public deploy/
cp server.js deploy/
cp package.json deploy/
cp next.config.ts deploy/
# Optional: Zip the files
echo "Zipping deployment files..."
cd deploy && zip -r ../deploy.zip . && cd ..
echo "Done! Upload 'deploy.zip' to your cPanel directory and follow the guide."

View File

@@ -8,6 +8,7 @@ const withPWA = withPWAInit({
} as any);
const nextConfig: NextConfig = {
output: 'standalone',
images: {
remotePatterns: [
{

19
server.js Normal file
View File

@@ -0,0 +1,19 @@
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
const port = process.env.PORT || 3000
app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
handle(req, res, parsedUrl)
}).listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View File

@@ -13,6 +13,16 @@ import { investors } from '@/data/mockData';
import toast from 'react-hot-toast';
import InvestorNotification from '@/components/InvestorNotification';
interface PaymentRecord {
id: string;
date: string;
amount: number;
installmentNo: number | null;
type: 'full' | 'partial' | 'installment';
method: string;
status: 'completed' | 'pending';
}
export default function InvestorInvestmentDetailPage({ params }: { params: Promise<{ id: string }> }) {
const resolvedParams = use(params);
const { id: investmentId } = resolvedParams;
@@ -23,7 +33,12 @@ export default function InvestorInvestmentDetailPage({ params }: { params: Promi
const [activeTab, setActiveTab] = useState('overview');
const [showPaymentModal, setShowPaymentModal] = useState(false);
const [selectedInstallment, setSelectedInstallment] = useState<'full' | '2' | '3'>('3');
const [paymentAmount, setPaymentAmount] = useState('');
const paymentHistory: PaymentRecord[] = [
{ id: 'pay1', date: '2024-01-15', amount: 400000, installmentNo: 1, type: 'installment', method: 'Bank Transfer', status: 'completed' },
{ id: 'pay2', date: '2024-02-15', amount: 150000, installmentNo: null, type: 'partial', method: 'bKash', status: 'completed' },
];
if (!investment) {
return (
@@ -42,6 +57,9 @@ export default function InvestorInvestmentDetailPage({ params }: { params: Promi
);
}
const totalPaid = paymentHistory.reduce((sum, p) => p.status === 'completed' ? sum + p.amount : sum, 0);
const dueAmount = investment.totalInvestment - totalPaid;
const planConfig: Record<string, { badge: string }> = {
silver: { badge: 'bg-slate-200 text-slate-700' },
gold: { badge: 'bg-amber-100 text-amber-700' },
@@ -70,16 +88,23 @@ export default function InvestorInvestmentDetailPage({ params }: { params: Promi
{ id: 'tx8', date: '2024-05-08', description: 'Rental Income - Bike CD-5678', amount: 500, status: 'completed' },
];
const dueAmount = investment.totalInvestment * 0.33;
const paidAmount = investment.totalInvestment * 0.67;
const handlePaymentSubmit = () => {
toast.success(`Payment of ৳${(dueAmount / (selectedInstallment === '2' ? 2 : 3)).toLocaleString()} initiated successfully!`);
const amount = parseFloat(paymentAmount);
if (!amount || amount <= 0) {
toast.error('Please enter a valid amount');
return;
}
if (amount > dueAmount) {
toast.error('Amount exceeds due amount');
return;
}
toast.success(`Payment of ৳${amount.toLocaleString()} submitted successfully!`);
setShowPaymentModal(false);
setPaymentAmount('');
};
return (
<div className="min-h-screen lg:pt-6 pt-0 ">
<div className="min-h-screen pb-20 lg:pb-0">
<InvestorNotification isMobile />
<div className="pt-18 lg:pt-0 p-4 lg:p-6 max-w-8xl mx-auto mb-20 lg:mb-0">
<div className="mb-6">
@@ -152,6 +177,7 @@ export default function InvestorInvestmentDetailPage({ params }: { params: Promi
{[
{ key: 'overview', label: 'Overview', icon: FileText, count: null },
{ key: 'bikes', label: 'Bikes', icon: Bike, count: demoBikes.length },
{ key: 'payments', label: 'Payments', icon: Wallet, count: null },
{ key: 'transactions', label: 'Transactions', icon: CreditCard, count: demoTransactions.length },
{ key: 'statement', label: 'Statement', icon: Receipt, count: null },
@@ -346,6 +372,62 @@ export default function InvestorInvestmentDetailPage({ params }: { params: Promi
</div>
</div>
)}
{activeTab === 'payments' && (
<div className="space-y-4">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
<p className="text-sm text-slate-500">{paymentHistory.length} payments made</p>
<button onClick={() => setShowPaymentModal(true)} className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark flex items-center gap-2">
<DollarSign className="w-4 h-4" /> Make Payment
</button>
</div>
<div className="overflow-x-auto">
<table className="hidden lg:table w-full text-left">
<thead className="bg-slate-50">
<tr>
<th className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase">Date</th>
<th className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase">Type</th>
<th className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase">Installment</th>
<th className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase">Method</th>
<th className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase text-right">Amount</th>
<th className="px-4 py-3 text-xs font-semibold text-slate-500 uppercase">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{paymentHistory.map((payment) => (
<tr key={payment.id} className="hover:bg-slate-50 transition-colors">
<td className="px-4 py-3 text-sm text-slate-600 whitespace-nowrap">{payment.date}</td>
<td className="px-4 py-3"><span className="px-2 py-0.5 rounded text-[10px] font-bold uppercase bg-slate-100 text-slate-600">{payment.type}</span></td>
<td className="px-4 py-3 text-sm text-slate-600">{payment.installmentNo ? `#${payment.installmentNo}` : '-'}</td>
<td className="px-4 py-3 text-sm text-slate-600">{payment.method}</td>
<td className="px-4 py-3 text-sm font-bold text-green-600 text-right">{payment.amount.toLocaleString()}</td>
<td className="px-4 py-3"><span className="inline-flex px-2 py-0.5 rounded text-[10px] font-bold uppercase bg-green-100 text-green-700">{payment.status}</span></td>
</tr>
))}
</tbody>
</table>
</div>
<div className="lg:hidden space-y-3">
{paymentHistory.map((payment) => (
<div key={payment.id} className="p-4 bg-white rounded-xl border border-slate-200">
<div className="flex items-start justify-between mb-2">
<div>
<p className="text-sm font-medium text-slate-800">{payment.amount.toLocaleString()}</p>
<p className="text-xs text-slate-400">{payment.date} {payment.method}</p>
</div>
<span className="px-2 py-0.5 rounded text-[10px] font-bold uppercase bg-green-100 text-green-700">{payment.status}</span>
</div>
<div className="flex gap-2 mt-2">
<span className="px-2 py-0.5 rounded text-[10px] font-bold uppercase bg-slate-100 text-slate-600">{payment.type}</span>
{payment.installmentNo && <span className="px-2 py-0.5 rounded text-[10px] font-bold uppercase bg-blue-100 text-blue-600">Inst #{payment.installmentNo}</span>}
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
</div>
@@ -354,55 +436,44 @@ export default function InvestorInvestmentDetailPage({ params }: { params: Promi
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-2xl w-full max-w-md shadow-2xl">
<div className="flex items-center justify-between p-5 border-b border-slate-100">
<h3 className="text-lg font-bold text-slate-800">Pay Due Amount</h3>
<h3 className="text-lg font-bold text-slate-800">Make Payment</h3>
<button onClick={() => setShowPaymentModal(false)} className="p-1 hover:bg-slate-100 rounded-lg">
<X className="w-5 h-5 text-slate-400" />
</button>
</div>
<div className="p-5 space-y-4">
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4 text-center">
<p className="text-sm text-amber-700 mb-1">Total Due</p>
<p className="text-3xl font-bold text-amber-600">{dueAmount.toLocaleString()}</p>
<div className="bg-slate-50 border border-slate-200 rounded-xl p-4">
<div className="flex justify-between mb-2">
<span className="text-sm text-slate-500">Total Investment</span>
<span className="text-sm font-semibold text-slate-800">{investment.totalInvestment.toLocaleString()}</span>
</div>
<div className="flex justify-between mb-2">
<span className="text-sm text-slate-500">Already Paid</span>
<span className="text-sm font-semibold text-green-600">{totalPaid.toLocaleString()}</span>
</div>
<div className="flex justify-between pt-2 border-t border-slate-200">
<span className="text-sm font-semibold text-slate-800">Due Amount</span>
<span className="text-sm font-bold text-amber-600">{dueAmount.toLocaleString()}</span>
</div>
</div>
<div>
<p className="text-sm font-semibold text-slate-700 mb-3">Installment List</p>
<div className="space-y-2">
<div className="p-3 bg-green-50 border border-green-200 rounded-lg flex items-center justify-between">
<div className="flex items-center gap-2">
<Check className="w-4 h-4 text-green-600" />
<div>
<p className="text-sm font-semibold text-slate-800">Installment 1</p>
<p className="text-xs text-slate-500">Paid on Jan 15, 2024</p>
</div>
</div>
<span className="text-sm font-bold text-green-600">{(investment.totalInvestment * 0.4).toLocaleString()}</span>
</div>
<div className="p-3 bg-amber-50 border border-amber-300 rounded-lg flex items-center justify-between">
<div className="flex items-center gap-2">
<Clock className="w-4 h-4 text-amber-600" />
<div>
<p className="text-sm font-semibold text-slate-800">Installment 2</p>
<p className="text-xs text-amber-600">Due: Jun 15, 2024</p>
</div>
</div>
<span className="text-sm font-bold text-amber-600">{(investment.totalInvestment * 0.3).toLocaleString()}</span>
</div>
<div className="p-3 bg-amber-50 border border-amber-300 rounded-lg flex items-center justify-between">
<div className="flex items-center gap-2">
<Clock className="w-4 h-4 text-amber-600" />
<div>
<p className="text-sm font-semibold text-slate-800">Installment 3</p>
<p className="text-xs text-amber-600">Due: Jul 15, 2024</p>
</div>
</div>
<span className="text-sm font-bold text-amber-600">{(investment.totalInvestment * 0.3).toLocaleString()}</span>
</div>
<label className="text-sm font-semibold text-slate-700 mb-2 block">Enter Amount</label>
<div className="relative">
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-slate-400"></span>
<input type="number" value={paymentAmount} onChange={(e) => setPaymentAmount(e.target.value)}
placeholder="Enter amount"
className="w-full pl-8 pr-4 py-3 border border-slate-200 rounded-xl text-lg font-semibold focus:outline-none focus:border-investor" />
</div>
<div className="flex gap-2 mt-2">
<button onClick={() => setPaymentAmount(dueAmount.toString())} className="px-3 py-1 text-xs bg-slate-100 text-slate-600 rounded-lg hover:bg-slate-200">Full Due</button>
<button onClick={() => setPaymentAmount((dueAmount / 2).toString())} className="px-3 py-1 text-xs bg-slate-100 text-slate-600 rounded-lg hover:bg-slate-200">Half</button>
<button onClick={() => setPaymentAmount((dueAmount / 4).toString())} className="px-3 py-1 text-xs bg-slate-100 text-slate-600 rounded-lg hover:bg-slate-200">25%</button>
</div>
</div>
<div className="pt-2">
<p className="text-sm font-semibold text-slate-700 mb-2">Select Payment</p>
<label className="text-sm font-semibold text-slate-700 mb-2 block">Payment Method</label>
<div className="flex gap-2">
<button className="flex-1 p-3 border border-slate-200 rounded-lg text-center hover:border-investor hover:bg-investor/5 transition-colors">
<Building2 className="w-5 h-5 mx-auto text-slate-600 mb-1" />
@@ -416,7 +487,7 @@ export default function InvestorInvestmentDetailPage({ params }: { params: Promi
</div>
<button onClick={handlePaymentSubmit} className="w-full py-3 bg-investor text-white rounded-xl font-bold hover:bg-investor-dark transition-colors">
Pay Now {(investment.totalInvestment * 0.3).toLocaleString()}
Pay {paymentAmount ? parseFloat(paymentAmount).toLocaleString() : '0'}
</button>
</div>
</div>