Spaces:
Sleeping
Sleeping
File size: 9,160 Bytes
dbc70ee | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | import React from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { X, Info, DollarSign, Shield } from 'lucide-react';
export default function TransparencyModal({ isOpen, onClose, data, currentPortfolio, totalValue, prices }) {
if (!isOpen || !data) return null;
return (
<AnimatePresence>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="absolute inset-0 bg-gs-navy/60 backdrop-blur-sm"
/>
{/* Modal Content */}
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="relative bg-white rounded-3xl shadow-2xl w-full max-w-lg max-h-[90vh] overflow-hidden border border-gray-100 flex flex-col"
>
{/* Header (Fixed at top) */}
<div className="bg-gs-navy p-6 flex justify-between items-start text-white shrink-0">
<div>
<span className="text-gs-gold text-xs font-semibold uppercase tracking-wider mb-2 block">Recommendation</span>
<h2 className="text-2xl font-light">{data.title}</h2>
</div>
<button onClick={onClose} className="text-white/60 hover:text-white transition-colors">
<X size={24} />
</button>
</div>
<div className="p-8 space-y-6 overflow-y-auto scrollbar-hide">
{/* The Advice */}
<div className="bg-gs-light p-5 rounded-xl border-l-4 border-gs-gold">
<h3 className="font-medium text-gs-navy mb-1 flex items-center">
<Info size={18} className="mr-2 text-gs-gold" /> The Action Plan
</h3>
<p className="text-gs-slate font-light text-lg">
{(() => {
let advice = data.advice;
const hasBonds = currentPortfolio?.allocation?.some(a =>
a.ticker?.includes('BND') ||
a.name?.toLowerCase().includes('bond')
);
if (!hasBonds && advice.includes('Bonds')) {
advice = advice.replace('Bonds', 'Cash Reserves');
}
return advice;
})()}
</p>
</div>
{/* Visual Comparison Section */}
<div className="bg-gs-light/50 p-6 rounded-2xl border border-gray-100 min-h-[100px] flex flex-col justify-center">
<h3 className="text-xs uppercase tracking-widest text-gs-slate font-semibold mb-4">
Visual Rebalance Suggestion
</h3>
<div className="space-y-6">
{currentPortfolio?.allocation?.length > 0 ? (
currentPortfolio.allocation.slice(0, 3).map((asset, idx) => {
if (!asset) return null;
// Mock logic for "Target" based on scenario
let change = 0;
const trigger = data.trigger || "";
const assetName = asset.name || "";
if (trigger.includes("Market Drop")) {
if (assetName.includes("Bond") || assetName.includes("Cash")) change = -5;
else change = 5;
} else if (trigger.includes("Life Expense")) {
if (assetName.includes("Cash")) change = 20;
else change = -10;
} else if (trigger.includes("Inflation")) {
if (assetName.includes("Vanguard") || assetName.includes("Value")) change = 8;
else change = -4;
}
const val = Number(asset.value) || 0;
const targetValue = Math.max(0, Math.min(100, val + change));
// Calculate Dollar and Share Changes
const ticker = asset.ticker;
const priceObj = prices[ticker];
const price = priceObj?.price;
// Use a fallback total value if current calculation is still in flight
const activeTotal = totalValue > 0 ? totalValue : 50000;
const currentValue = (val / 100) * activeTotal;
const targetValueDollar = (targetValue / 100) * activeTotal;
const dollarDiff = targetValueDollar - currentValue;
const sharesDiff = price ? Math.abs(Math.round(dollarDiff / price)) : null;
return (
<div key={idx} className="space-y-2">
<div className="flex justify-between text-xs">
<span className="font-bold text-gs-navy">{ticker}</span>
<div className="text-right">
<span className="text-gs-slate">
{val.toFixed(2)}% <span className="mx-2">→</span>
<span className={change > 0 ? 'text-green-600' : change < 0 ? 'text-red-500' : 'text-gs-navy'}>
{targetValue.toFixed(2)}%
</span>
</span>
<p className={`text-[10px] font-bold ${change > 0 ? 'text-green-600' : 'text-red-500'}`}>
{change > 0 ? 'Buy' : 'Sell'} ${Math.abs(Math.round(dollarDiff)).toLocaleString()}
{sharesDiff !== null ? ` (${sharesDiff} shares)` : ' (Calculating...)'}
</p>
</div>
</div>
<div className="h-3 w-full bg-gray-200 rounded-full overflow-hidden flex relative">
<div
className="h-full bg-gs-navy opacity-30"
style={{ width: `${val}%` }}
></div>
<div
className={`h-full absolute top-0 left-0 transition-all duration-1000 ${change >= 0 ? 'bg-green-500' : 'bg-red-500'}`}
style={{ width: `${targetValue}%` }}
></div>
</div>
</div>
);
})
) : (
<div className="text-center py-4">
<p className="text-xs text-gs-slate italic">Add at least one stock to see a visual rebalance simulation.</p>
</div>
)}
</div>
<p className="text-[10px] text-gs-slate mt-4 italic text-center">
*Simulated shift based on your {currentPortfolio?.riskLevel || 'selected'} risk profile.
</p>
</div>
{/* The Why */}
<div>
<h3 className="font-medium text-gs-navy mb-2 flex items-center">
<Shield size={18} className="mr-2 text-gs-slate" /> Why we recommend this
</h3>
<p className="text-gs-slate font-light text-sm leading-relaxed">
{data.explanation}
</p>
</div>
<hr className="border-gray-100" />
{/* Radical Transparency Section */}
<div>
<h3 className="text-xs uppercase tracking-widest text-gs-slate font-semibold mb-4">
Full Transparency
</h3>
<div className="space-y-3">
<div className="flex justify-between items-center p-3 rounded-lg border border-gray-100 bg-white">
<span className="text-sm text-gs-slate font-light flex items-center">
<DollarSign size={14} className="mr-2 text-gray-400" /> Fee Impact
</span>
<span className="font-medium text-gs-navy text-sm">{data.feeImpactDollars}</span>
</div>
<div className="flex justify-between items-center p-3 rounded-lg border border-gray-100 bg-white">
<span className="text-sm text-gs-slate font-light flex items-center">
<Shield size={14} className="mr-2 text-gray-400" /> Tax Considerations
</span>
<span className="font-medium text-gs-navy text-sm">{data.taxImpact}</span>
</div>
</div>
</div>
<button
onClick={onClose}
className="w-full mt-4 bg-gs-navy text-white py-4 rounded-xl font-medium hover:bg-gs-navy/90 transition-colors shadow-md shrink-0"
>
I Understand
</button>
</div>
</motion.div>
</div>
</AnimatePresence>
);
}
|