Files
SkyMoney/web/src/components/FundingConfirmationModal.tsx

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>
);
}