feat: add payment tracking and manual payment submission to investment details and configure standalone deployment mode
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -50,3 +50,6 @@ next-env.d.ts
|
||||
|
||||
**/docs
|
||||
**/.docs
|
||||
|
||||
**/deploy.zip
|
||||
**/deploy
|
||||
20
deploy.sh
Executable file
20
deploy.sh
Executable 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."
|
||||
@@ -8,6 +8,7 @@ const withPWA = withPWAInit({
|
||||
} as any);
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: 'standalone',
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
|
||||
19
server.js
Normal file
19
server.js
Normal 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}`)
|
||||
})
|
||||
})
|
||||
@@ -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>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user