final touches for beta skymoney (at least i think)
This commit is contained in:
143
web/src/components/FundingConfirmationModal.tsx
Normal file
143
web/src/components/FundingConfirmationModal.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user