Merge 344b7a9fbb27f75a0d6f8f063d7e1309ee4b53fa into 5936eb301fa8bd7a5fbe640bba8d0a7819eb677a

This commit is contained in:
A₿del ∞/21M 2025-04-10 10:18:55 -04:00 committed by GitHub
commit e5f87fcd75
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 131 additions and 87 deletions

View File

@ -0,0 +1,89 @@
import { useState, useEffect, useRef } from "react";
interface BitcoinPriceData {
bitcoinPrice: number;
priceDirection: string | null;
error: string | null;
countdown: number;
isFetching: boolean;
}
export function useBitcoinPrice(): BitcoinPriceData {
const [bitcoinPrice, setBitcoinPrice] = useState(0);
const previousPriceRef = useRef(0);
const [priceDirection, setPriceDirection] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [countdown, setCountdown] = useState(20);
const [isFetching, setIsFetching] = useState(true); // Start as true initially
useEffect(() => {
const fetchBitcoinPrice = async () => {
setIsFetching(true);
setError(null); // Clear previous errors
try {
const response = await fetch(
"https://pricing.bitcoin.block.xyz/current-price"
);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
const newPrice = parseFloat(data["amount"]);
// Only update direction if not the very first fetch
if (previousPriceRef.current !== 0) {
if (newPrice > previousPriceRef.current) {
setPriceDirection("↑");
} else if (newPrice < previousPriceRef.current) {
setPriceDirection("↓");
} else {
setPriceDirection(null); // Explicitly set to null if equal
}
// Reset direction indicator after a delay
if (newPrice !== previousPriceRef.current) {
setTimeout(() => {
setPriceDirection(null);
}, 2000);
}
} else {
setPriceDirection(null); // No direction on first load
}
setBitcoinPrice(newPrice);
previousPriceRef.current = newPrice; // Update previous price *after* comparison
} catch (err) {
console.error("Failed to fetch Bitcoin price:", err);
setError("API Error"); // Simpler error message
setBitcoinPrice(0); // Reset price on error
setPriceDirection(null);
} finally {
setIsFetching(false);
setCountdown(20); // Reset countdown after fetch attempt
}
};
// Fetch immediately on load
fetchBitcoinPrice();
// Set up countdown interval
const countdownInterval = setInterval(() => {
setCountdown((prev) => {
if (prev <= 1) {
fetchBitcoinPrice(); // Fetch when countdown reaches 0
return 20; // Reset to 20 seconds
}
return prev - 1;
});
}, 1000);
// Clean up intervals on component unmount
return () => {
clearInterval(countdownInterval);
};
}, []); // Empty dependency array means this runs once on mount and cleans up on unmount
return { bitcoinPrice, priceDirection, error, countdown, isFetching };
}

View File

@ -1,8 +1,9 @@
"use client";
import { SolariBoard } from "./components/solari/SolariBoard";
import { useState, useEffect, useMemo, useRef, Suspense } from "react";
import { useState, useEffect, useMemo, Suspense } from "react";
import { useSearchParams, useRouter } from "next/navigation";
import { useBitcoinPrice } from "./hooks/useBitcoinPrice";
// import { useDisplayLength } from "./components/useDisplayLength";
function formatCurrency(number: number, locale = "en-US", currency = "USD") {
@ -26,6 +27,7 @@ const getLoadingRows = (displayLength: number) => [
{ value: "", length: displayLength },
{ value: "", length: displayLength },
{ value: "", length: displayLength },
{ value: "", length: displayLength },
];
function HomeContent() {
@ -34,17 +36,31 @@ function HomeContent() {
// const displayLength = useDisplayLength();
const displayLength = 20; // Fallback to a fixed length for simplicity
const [bitcoinPrice, setBitcoinPrice] = useState(0);
const previousPriceRef = useRef(0);
const [priceDirection, setPriceDirection] = useState<string | null>(null);
const [holding] = useState(8485);
// Use the custom hook for price data
const { bitcoinPrice, priceDirection, error, countdown, isFetching } =
useBitcoinPrice();
// Get holding from URL, default to 40
const getHoldingFromParams = () => {
const holdingParam = searchParams.get("holding");
if (holdingParam) {
const parsedHolding = parseFloat(holdingParam);
// Validate if it's a positive number
if (!isNaN(parsedHolding) && parsedHolding > 0) {
return parsedHolding;
}
// Optionally: set an error or redirect if invalid?
// For now, just default back.
}
return 8485; // Default value
};
const [holding, setHolding] = useState(getHoldingFromParams);
const [holdingValue, setHoldingValue] = useState(0);
const [currentRowIndex, setCurrentRowIndex] = useState(-1);
const [ticker, setTicker] = useState(searchParams.get("ticker") || "XYZ");
const [inputError, setInputError] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [countdown, setCountdown] = useState(20);
const [isFetching, setIsFetching] = useState(false);
// Removed inputError as it wasn't used
// Removed explicit error, countdown, isFetching states - now handled by hook
// Initialize loading rows immediately
const loadingBoardRows = useMemo(
@ -57,14 +73,18 @@ function HomeContent() {
setHoldingValue(bitcoinPrice * holding);
}, [bitcoinPrice, holding]);
// Update holding if URL param changes
useEffect(() => {
setHolding(getHoldingFromParams());
}, [searchParams]); // Re-run when searchParams change
// Format the display values
const displayValue = error
? "Error"
: `${formatCurrency(bitcoinPrice).toString()}${
priceDirection ? ` ${priceDirection}` : ""
}`;
? error // Display the error message from the hook
: `${formatCurrency(bitcoinPrice).toString()}${priceDirection ? ` ${priceDirection}` : ""}`;
const holdingDisplay = error ? "Error" : formatCurrency(holdingValue);
const holdingDisplayBTC = error ? "Error" : `${holding} BTC`;
// Define the final board rows
const finalBoardRows = useMemo(
@ -72,14 +92,15 @@ function HomeContent() {
{ value: "", length: displayLength },
{ value: ` ${ticker}`, length: displayLength },
{ value: "", length: displayLength },
{ value: " TOTAL HOLDING", length: displayLength },
{ value: ` ${holdingDisplayBTC}`, length: displayLength, color: "#FFA500" },
{ value: " TOTAL HOLDING USD", length: displayLength },
{ value: ` ${holdingDisplay}`, length: displayLength },
{ value: "", length: displayLength },
{ value: " BTC PRICE", length: displayLength },
{ value: ` ${displayValue}`, length: displayLength },
{ value: "", length: displayLength },
],
[ticker, holdingDisplay, displayValue, displayLength]
[ticker, holdingDisplay, holdingDisplayBTC, displayValue, displayLength]
);
// Current board rows based on loading state and animation progress
@ -98,7 +119,8 @@ function HomeContent() {
// Handle the row-by-row animation
useEffect(() => {
if (!isFetching && currentRowIndex === -1) {
// Start animation only when initial fetch is done AND no error
if (!isFetching && currentRowIndex === -1 && !error) {
// Start the row animation after data is loaded
const animateRows = () => {
const interval = setInterval(() => {
@ -117,80 +139,13 @@ function HomeContent() {
// Small delay before starting the animation
setTimeout(animateRows, 1000);
}
}, [isFetching, currentRowIndex, finalBoardRows.length]);
// Fetch Bitcoin price and manage countdown
useEffect(() => {
const fetchBitcoinPrice = async () => {
setIsFetching(true);
try {
const response = await fetch(
"https://pricing.bitcoin.block.xyz/current-price"
);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
const newPrice = parseFloat(data["amount"]);
// Check if this is not the first fetch
if (!isFetching) {
// Compare with previous price to determine direction
if (newPrice > previousPriceRef.current) {
setPriceDirection("↑");
} else if (newPrice < previousPriceRef.current) {
setPriceDirection("↓");
} else {
setPriceDirection(null);
}
// Remove the direction indicator after 5 seconds (increased from 2 seconds)
if (newPrice !== previousPriceRef.current) {
setTimeout(() => {
setPriceDirection(null);
}, 2000);
}
} else {
// Set initial price without showing direction
setPriceDirection(null);
}
// Update prices
const oldPrice = previousPriceRef.current;
previousPriceRef.current = newPrice;
setBitcoinPrice(newPrice);
} catch (err) {
console.error("Failed to fetch Bitcoin price:", err);
setError("Failed to fetch Bitcoin price");
}
setIsFetching(false);
setCountdown(20);
};
// Fetch immediately on load
fetchBitcoinPrice();
// Set up countdown interval
const countdownInterval = setInterval(() => {
setCountdown((prev) => {
if (prev <= 1) {
fetchBitcoinPrice(); // Fetch when countdown reaches 0
return 20; // Reset to 20 seconds
}
return prev - 1;
});
}, 1000);
// Clean up intervals on component unmount
return () => {
clearInterval(countdownInterval);
};
}, []);
}, [isFetching, currentRowIndex, finalBoardRows.length, error]); // Added error dependency
return (
<div className="w-full h-full font-mono flex flex-col justify-center items-center">
{/* Input field for holding - Optional feature */}
{/* Consider adding an input field here if direct editing is desired */}
<div className="relative p-4 rounded-lg bg-[#0e0d0d]">
<SolariBoard rows={currentBoardRows} className="relative" />
</div>