From 15bb6f91663d227cb4e2fc2f1dde3bd070e7774d Mon Sep 17 00:00:00 2001 From: Yousef Saad Date: Tue, 14 Oct 2025 11:12:56 +0100 Subject: [PATCH] feat: format large numbers with compact display (1.2B, 900M, etc) --- src/pages/Crypto.jsx | 4 +- src/pages/Weather.jsx | 370 +++++++++++++++---------------- src/utilities/numberFormatter.js | 9 + 3 files changed, 192 insertions(+), 191 deletions(-) create mode 100644 src/utilities/numberFormatter.js diff --git a/src/pages/Crypto.jsx b/src/pages/Crypto.jsx index 645d268..cc1d12c 100644 --- a/src/pages/Crypto.jsx +++ b/src/pages/Crypto.jsx @@ -22,7 +22,7 @@ import { useEffect, useState } from 'react'; import Loading from '../components/Loading.jsx'; import ErrorMessage from '../components/ErrorMessage.jsx'; import Card from '../components/Card.jsx'; - +import formatNumber from '../utilities/numberFormatter.js'; export default function Crypto() { const [coins, setCoins] = useState([]); const [query, setQuery] = useState(''); @@ -52,7 +52,7 @@ export default function Crypto() {
{filtered.map(c => ( ${c.current_price}}> -

Market Cap: ${c.market_cap.toLocaleString()}

+

Market Cap: ${formatNumber(c.market_cap)}

24h: {c.price_change_percentage_24h?.toFixed(2)}%

{/* TODO: Add mini sparkline chart */}
diff --git a/src/pages/Weather.jsx b/src/pages/Weather.jsx index 8060f80..44ace30 100644 --- a/src/pages/Weather.jsx +++ b/src/pages/Weather.jsx @@ -71,35 +71,83 @@ function renderWeatherAnimation(variant) { return ( <> {/* Inline SVG cloud instances for predictable lobe placement */} - + - + - + - + - + - - + + - + - + - - + + @@ -139,8 +187,8 @@ function renderWeatherAnimation(variant) { left: `${(i / 12) * 100}%`, animationDelay: `${(i % 6) * 0.4}s`, // duration and horizontal drift vary per-flake - '--dur': `${10 + (i % 6)}s`, - '--drift': `${(i % 2 === 0 ? -40 : 40)}px`, + "--dur": `${10 + (i % 6)}s`, + "--drift": `${i % 2 === 0 ? -40 : 40}px`, width: `${8 + (i % 3) * 4}px`, height: `${8 + (i % 3) * 4}px`, opacity: 0.9, @@ -157,8 +205,8 @@ function renderWeatherAnimation(variant) { style={{ left: `${(i / 16) * 100}%`, animationDelay: `${(i % 5) * 0.15}s`, - '--dur': `${6 + (i % 5)}s`, - '--drift': `${(i % 3 === 0 ? -24 : 24)}px`, + "--dur": `${6 + (i % 5)}s`, + "--drift": `${i % 3 === 0 ? -24 : 24}px`, width: `${6 + (i % 2) * 3}px`, height: `${6 + (i % 2) * 3}px`, opacity: 0.98, @@ -202,7 +250,7 @@ function renderWeatherAnimation(variant) { ); } - // Default: subtle particle shimmer + // Default: subtle particle shimmer return ( <>
@@ -275,7 +323,7 @@ export default function Weather() { const current = data?.current_condition?.[0]; const forecast = data?.weather?.slice(0, 3) || []; - const desc = current?.weatherDesc?.[0]?.value || ""; + const desc = current?.weatherDesc?.[0]?.value || ""; const weatherBg = weatherToClass(desc); const weatherVariant = weatherBg.replace("weather-bg-", ""); @@ -366,6 +414,7 @@ export default function Weather() { transition: "background 1s ease-in-out", }} > + {/* Background Animation Layer */}
+ {/* Header */}
+ className="dashboard-header" + style={{ position: "relative", zIndex: 10 }} + >

🌤️ Weather Dashboard

- {loading && } - {error && ( - fetchWeather(city)} - /> - )} - - {data && !loading && ( -
- {/* Current Weather */} - -

{data.nearest_area?.[0]?.areaName?.[0]?.value || city}

-

- {current && getIconUrl(current.weatherIconUrl) && ( - {current.weatherDesc?.[0]?.value - )} - - Temperature:{" "} - {displayTemp(Number(current.temp_C))}°{unit} - -

-

- Humidity: {current.humidity}% -

-

- Desc: {current.weatherDesc?.[0]?.value} -

-
- - {/* 3-Day Forecast */} - {forecast.map((day, i) => { - const condition = - day.hourly?.[0]?.weatherDesc?.[0]?.value || "Clear"; - const badge = getBadgeStyle(condition); - - return ( - - {/* Badge Section */} - {/* Forecast icon (use first hourly entry icon) */} - {day.hourly?.[0] && - getIconUrl(day.hourly?.[0]?.weatherIconUrl) && ( -
- { - (e.currentTarget.style.display = "none") - } - /> -
- )} - - {/* Full-day hourly timeline (0:00 - 23:00) */} - {day.hourly && ( -
-
- {day.hourly.map((h, idx) => { - const icon = getIconUrl(h.weatherIconUrl); - const t = h.time ?? h.Time ?? ""; - const temp = - h.tempC ?? h.temp_C ?? h.tempC ?? h.tempF ?? ""; - - return ( -
- {icon ? ( - {h.weatherDesc?.[0]?.value - (e.currentTarget.style.display = "none") - } - /> - ) : ( -
🌤️
- )} -
- {formatWttTime(t)} -
-
- {displayTemp(Number(temp))}°{unit} -
-
- ); - })} -
-
- )} - -
- Avg Temp: {displayTemp(Number(day.avgtempC))} - °{unit} -
- {badge.label} -
-
-

- Sunrise: {day.astronomy?.[0]?.sunrise} -

-

- Sunset: {day.astronomy?.[0]?.sunset} -

-
- ); - })}
+ {/* Loading State */} {loading && } + + {/* Error State */} {error && ( )} + {/* Content Grid */} {data && !loading && ( -
- {/* Current Weather */} + {/* Current Weather Card */}

{data.nearest_area?.[0]?.areaName?.[0]?.value || city}

-

- Temperature:{" "} - {displayTemp(Number(current.temp_C))}°{unit} +

+ {current && getIconUrl(current.weatherIconUrl) && ( + {current.weatherDesc?.[0]?.value + )} + + Temperature:{" "} + {displayTemp(Number(current.temp_C))}°{unit} +

Humidity: {current.humidity}% @@ -599,7 +504,7 @@ export default function Weather() {

- {/* 3-Day Forecast */} + {/* 3-Day Forecast Cards */} {forecast.map((day, i) => { const condition = day.hourly?.[0]?.weatherDesc?.[0]?.value || "Clear"; @@ -607,7 +512,7 @@ export default function Weather() { return ( - {/* Badge Section */} + {/* Weather Badge */}
-

- Avg Temp: {displayTemp(Number(day.avgtempC))} - °{unit} -

+ {/* Forecast Icon */} + {day.hourly?.[0] && + getIconUrl(day.hourly?.[0]?.weatherIconUrl) && ( +
+ { + (e.currentTarget.style.display = "none") + } + /> +
+ )} + + {/* Hourly Timeline */} + {day.hourly && ( +
+
+ {day.hourly.map((h, idx) => { + const icon = getIconUrl(h.weatherIconUrl); + const t = h.time ?? h.Time ?? ""; + const temp = h.tempC ?? h.temp_C ?? h.tempF ?? ""; + + return ( +
+ {icon ? ( + {h.weatherDesc?.[0]?.value + (e.currentTarget.style.display = "none") + } + /> + ) : ( +
🌤️
+ )} +
+ {formatWttTime(t)} +
+
+ {displayTemp(Number(temp))}°{unit} +
+
+ ); + })} +
+
+ )} + + {/* Day Summary */} +
+ Avg Temp:{" "} + {displayTemp(Number(day.avgtempC))}°{unit} +

Sunrise: {day.astronomy?.[0]?.sunrise}

diff --git a/src/utilities/numberFormatter.js b/src/utilities/numberFormatter.js new file mode 100644 index 0000000..bdcebd8 --- /dev/null +++ b/src/utilities/numberFormatter.js @@ -0,0 +1,9 @@ +export default function formatNumber(number) { + return new Intl.NumberFormat("en", { + notation: "compact", + + maximumFractionDigits: 1, + }).format(number); +} + +// TODO: we can make here different type of currency