144 lines
4.6 KiB
TypeScript
144 lines
4.6 KiB
TypeScript
import { useState } from "react";
|
|
import { fixedPlansApi } from "../api/fixedPlans";
|
|
import { Money } from "./ui";
|
|
|
|
type FundingConfirmationModalProps = {
|
|
planId: string;
|
|
planName: string;
|
|
totalCents: number;
|
|
fundedCents: number;
|
|
availableBudget: number;
|
|
onClose: () => void;
|
|
onFundingComplete: (result: {
|
|
totalCents: number;
|
|
fundedCents: number;
|
|
isOverdue?: boolean;
|
|
overdueAmount?: number;
|
|
message?: string;
|
|
}) => void;
|
|
};
|
|
|
|
export default function FundingConfirmationModal({
|
|
planId,
|
|
planName,
|
|
totalCents,
|
|
fundedCents,
|
|
availableBudget,
|
|
onClose,
|
|
onFundingComplete,
|
|
}: FundingConfirmationModalProps) {
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
const [error, setError] = useState("");
|
|
|
|
const shortfall = totalCents - fundedCents;
|
|
const canFund = availableBudget >= shortfall;
|
|
|
|
const handleRemindLater = () => {
|
|
// Store dismissal timestamp in localStorage (4 hours from now)
|
|
const dismissedUntil = Date.now() + (4 * 60 * 60 * 1000); // 4 hours
|
|
localStorage.setItem(`overdue-dismissed-${planId}`, dismissedUntil.toString());
|
|
onClose();
|
|
};
|
|
|
|
const handlePullFromBudget = async () => {
|
|
setError("");
|
|
setIsProcessing(true);
|
|
|
|
try {
|
|
const result = await fixedPlansApi.attemptFinalFunding(planId);
|
|
onFundingComplete(result);
|
|
} catch (err: any) {
|
|
setError(err?.message || "Failed to fund from available budget");
|
|
setIsProcessing(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-[color:var(--color-bg)]/80 backdrop-blur-sm flex items-center justify-center z-50">
|
|
<div className="bg-[--color-bg] border rounded-xl p-6 max-w-md mx-4 space-y-4 shadow-2xl">
|
|
{/* Header */}
|
|
<div className="space-y-1">
|
|
<h3 className="font-semibold text-lg">{planName} is due today</h3>
|
|
<p className="text-sm muted">
|
|
This bill is not fully funded yet.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Funding Details */}
|
|
<div className="space-y-2 p-3 bg-gray-800/30 rounded-lg">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="muted">Total amount:</span>
|
|
<span className="font-mono"><Money cents={totalCents} /></span>
|
|
</div>
|
|
<div className="flex justify-between text-sm">
|
|
<span className="muted">Currently funded:</span>
|
|
<span className="font-mono"><Money cents={fundedCents} /></span>
|
|
</div>
|
|
<div className="border-t border-gray-700 pt-2 mt-2">
|
|
<div className="flex justify-between text-sm font-semibold text-amber-700 dark:text-yellow-400">
|
|
<span>Still needed:</span>
|
|
<span className="font-mono"><Money cents={shortfall} /></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Available Budget */}
|
|
<div className="p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="muted">Available budget:</span>
|
|
<span className="font-mono"><Money cents={availableBudget} /></span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Message */}
|
|
{canFund ? (
|
|
<div className="text-sm text-center muted">
|
|
Would you like to pull <Money cents={shortfall} /> from your available budget to fully fund this payment?
|
|
</div>
|
|
) : (
|
|
<div className="p-3 bg-red-500/10 border border-red-500 rounded-lg text-sm text-red-400">
|
|
Insufficient available budget. You need <Money cents={shortfall - availableBudget} /> more to fully fund this payment.
|
|
</div>
|
|
)}
|
|
|
|
{/* Error */}
|
|
{error && (
|
|
<div className="p-3 bg-red-500/10 border border-red-500 rounded-lg text-sm text-red-400">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{/* Actions */}
|
|
<div className="flex gap-3 pt-2">
|
|
<button
|
|
type="button"
|
|
className="btn bg-yellow-600 hover:bg-yellow-500"
|
|
onClick={handleRemindLater}
|
|
disabled={isProcessing}
|
|
>
|
|
Remind Later
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="btn bg-gray-600 hover:bg-gray-500"
|
|
onClick={onClose}
|
|
disabled={isProcessing}
|
|
>
|
|
Cancel
|
|
</button>
|
|
{canFund && (
|
|
<button
|
|
type="button"
|
|
className="btn ml-auto"
|
|
onClick={handlePullFromBudget}
|
|
disabled={isProcessing}
|
|
>
|
|
{isProcessing ? "Processing..." : "Pull from Budget"}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|