From 273f2d7bf85fb67d49b119473ee7dd1bca348026 Mon Sep 17 00:00:00 2001 From: meetdhorajiya Date: Sun, 19 Oct 2025 21:54:10 +0530 Subject: [PATCH] feat: Add 24h profit/loss calculator modal --- src/components/CryptoCalculatorModal.jsx | 157 +++++++++++++++++++++++ src/pages/Crypto.jsx | 28 +++- src/styles.css | 134 +++++++++++++++++++ 3 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 src/components/CryptoCalculatorModal.jsx diff --git a/src/components/CryptoCalculatorModal.jsx b/src/components/CryptoCalculatorModal.jsx new file mode 100644 index 0000000..57ba024 --- /dev/null +++ b/src/components/CryptoCalculatorModal.jsx @@ -0,0 +1,157 @@ +import React, { useState, useEffect, useRef } from 'react'; + +export default function CryptoCalculatorModal({ coinData, onClose }) { + const [amount, setAmount] = useState(''); + const [selectedCoinId, setSelectedCoinId] = useState(coinData[0]?.id || ''); + + const [searchTerm, setSearchTerm] = useState(''); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const dropdownRef = useRef(null); + + const [result, setResult] = useState(null); + + useEffect(() => { + setResult(null); + }, [amount, selectedCoinId]); + + useEffect(() => { + function handleClickOutside(event) { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setIsDropdownOpen(false); + } + } + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [dropdownRef]); + + + const isButtonDisabled = !amount || parseFloat(amount) <= 0; + + const handleCalculate = () => { + const coin = coinData.find((c) => c.id === selectedCoinId); + const inputAmount = parseFloat(amount); + if (!coin) return; + + const changePercent = coin.price_change_percentage_24h; + const changeDecimal = changePercent / 100; + const profitOrLoss = inputAmount * changeDecimal; + const finalAmount = inputAmount + profitOrLoss; + + setResult({ + finalAmount: finalAmount.toFixed(2), + profitOrLoss: profitOrLoss.toFixed(2), + isProfit: profitOrLoss >= 0, + error: null, + }); + }; + + const filteredCoins = coinData.filter(coin => + coin.name.toLowerCase().includes(searchTerm.toLowerCase()) || + coin.symbol.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + const selectedCoin = coinData.find(c => c.id === selectedCoinId); + + return ( +
+
e.stopPropagation()}> + +

24h Profit/Loss Calculator

+ +
+ + setAmount(e.target.value)} + placeholder="e.g., 100" + className="modal-input" + /> +
+ +
+ +
+ { + setSearchTerm(e.target.value); + setIsDropdownOpen(true); + }} + onFocus={() => setIsDropdownOpen(true)} + placeholder="Search coin..." + /> + {isDropdownOpen && ( +
+ {filteredCoins.length > 0 ? ( + filteredCoins.map((coin) => ( +
{ + setSelectedCoinId(coin.id); + setSearchTerm(''); + setIsDropdownOpen(false); + }} + > + {coin.symbol} + {coin.name} ({coin.symbol.toUpperCase()}) +
+ )) + ) : ( +
+ No results found +
+ )} +
+ )} +
+
+ + + + {result && ( +
+ {result.error ? ( +

{result.error}

+ ) : ( + <> +

Your ${amount} invested 24h ago would now be worth:

+

+ ${result.finalAmount} +

+

+ A {result.isProfit ? 'profit' : 'loss'} of{' '} + + {result.isProfit ? '+' : ''}${result.profitOrLoss} + +

+ + )} +
+ )} +
+
+ ); +} diff --git a/src/pages/Crypto.jsx b/src/pages/Crypto.jsx index 3d659e7..0781f34 100644 --- a/src/pages/Crypto.jsx +++ b/src/pages/Crypto.jsx @@ -26,7 +26,7 @@ import Card from "../components/Card.jsx"; import formatNumber from "../utilities/numberFormatter.js"; import HeroSection from '../components/HeroSection'; import CryptoImg from '../Images/Cryptocurrency.jpg'; - +import CryptoCalculatorModal from "../components/CryptoCalculatorModal.jsx"; export default function Crypto() { const [coins, setCoins] = useState([]); @@ -34,6 +34,7 @@ export default function Crypto() { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [page, setPage] = useState(1); + const [isCalculatorOpen, setIsCalculatorOpen] = useState(false); useEffect(() => { fetchCoins(); @@ -79,6 +80,23 @@ export default function Crypto() { style={{ marginBottom: "1rem" }} /> +
+ +
+ {loading && } @@ -134,6 +152,14 @@ export default function Crypto() { + + {isCalculatorOpen && coins.length > 0 && ( + setIsCalculatorOpen(false)} + /> + )} + ); } diff --git a/src/styles.css b/src/styles.css index e395cd2..751b1c4 100644 --- a/src/styles.css +++ b/src/styles.css @@ -922,4 +922,138 @@ blockquote { } } +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + animation: fadeIn 0.2s ease-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.modal { + background-color: var(--bg); + color: var(--text); + padding: 2rem; + border-radius: var(--radius); + width: 90%; + max-width: 500px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); + position: relative; + animation: modalSlideIn 0.3s ease-out; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.modal-close-button { + position: absolute; + top: 1rem; + right: 1rem; + background: transparent; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: var(--text); + opacity: 0.7; + transition: opacity 0.2s ease; +} + +.modal-close-button:hover { + opacity: 1; +} +.modal-input-group { + margin-bottom: 1rem; +} + +.modal-input-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: bold; +} + +.modal-input { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--bg-alt); + color: var(--text); + box-sizing: border-box; +} + +.modal-button { + width: 100%; + padding: 0.75rem; + font-size: 1rem; +} + +.modal-result { + margin-top: 1.5rem; + padding: 1rem; + border: 1px solid var(--border); + border-radius: var(--radius); + background-color: var(--bg-alt); + text-align: center; +} + +.modal-dropdown-container { + position: relative; +} + +.modal-dropdown-list { + position: absolute; + top: 100%; + left: 0; + right: 0; + background-color: var(--bg); + border: 1px solid var(--border); + border-top: none; + border-radius: 0 0 var(--radius) var(--radius); + max-height: 150px; + overflow-y: auto; + z-index: 1001; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.modal-dropdown-item, +.modal-dropdown-item-none { + padding: 0.75rem; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; +} + +.modal-dropdown-item:hover { + background-color: var(--bg-alt); +} + +.modal-dropdown-item-none { + font-style: italic; + opacity: 0.7; + cursor: default; +}