2024-05-28 02:13:04 +02:00
|
|
|
import { useEffect, useState } from "react";
|
2025-12-01 22:57:12 +01:00
|
|
|
import Layout from "../components/Layout";
|
|
|
|
|
import RateCard from "../components/RateCard";
|
2024-05-28 02:13:04 +02:00
|
|
|
import { fetchData } from "../utils/api";
|
2024-01-14 16:30:19 +01:00
|
|
|
|
|
|
|
|
export default function Rates() {
|
|
|
|
|
const [data, setData] = useState(null);
|
2024-01-14 16:53:56 +01:00
|
|
|
const [loading, setLoading] = useState(true);
|
2025-12-01 22:23:08 +01:00
|
|
|
const [error, setError] = useState(null);
|
2024-01-14 16:30:19 +01:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const fetchDataFromApi = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const apiData = await fetchData();
|
|
|
|
|
setData(apiData);
|
2025-12-01 22:23:08 +01:00
|
|
|
setError(null);
|
2024-01-14 16:30:19 +01:00
|
|
|
} catch (error) {
|
2024-05-28 02:13:04 +02:00
|
|
|
console.error("Error fetching data:", error);
|
2025-12-01 22:23:08 +01:00
|
|
|
setError("Failed to load exchange rates. Please try again later.");
|
2024-01-14 16:53:56 +01:00
|
|
|
} finally {
|
2025-12-01 22:23:08 +01:00
|
|
|
setLoading(false);
|
2024-01-14 16:30:19 +01:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fetchDataFromApi();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const formatCurrency = (value) => {
|
2024-05-28 02:13:04 +02:00
|
|
|
return new Intl.NumberFormat("en-NG", {
|
|
|
|
|
style: "currency",
|
|
|
|
|
currency: "NGN",
|
2024-01-14 16:30:19 +01:00
|
|
|
}).format(value);
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-01 22:23:08 +01:00
|
|
|
const getBestRate = () => {
|
|
|
|
|
if (!data || data.length === 0) return null;
|
|
|
|
|
return Math.max(...data.map(item => item.rate));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const bestRate = getBestRate();
|
|
|
|
|
|
|
|
|
|
const sortedData = data ? [...data].sort((a, b) => {
|
|
|
|
|
const aIsBest = a.bestRate || a.rate === bestRate;
|
|
|
|
|
const bIsBest = b.bestRate || b.rate === bestRate;
|
|
|
|
|
if (aIsBest && !bIsBest) return -1;
|
|
|
|
|
if (!aIsBest && bIsBest) return 1;
|
|
|
|
|
return b.rate - a.rate;
|
|
|
|
|
}) : null;
|
|
|
|
|
|
2024-01-14 16:30:19 +01:00
|
|
|
return (
|
2025-12-01 22:57:12 +01:00
|
|
|
<Layout
|
|
|
|
|
title="EUR to NGN Exchange Rates - Jesse Egbosionu"
|
|
|
|
|
description="Compare live EUR to NGN exchange rates across multiple providers."
|
|
|
|
|
showNavLinks={true}
|
|
|
|
|
>
|
|
|
|
|
<section className="rates-section">
|
|
|
|
|
<div className="rates-header">
|
|
|
|
|
<div className="rates-title-group">
|
|
|
|
|
<h1 className="rates-title">Exchange Rates</h1>
|
|
|
|
|
<p className="rates-subtitle">
|
|
|
|
|
Compare live EUR to NGN rates across providers
|
|
|
|
|
</p>
|
2025-12-01 22:23:08 +01:00
|
|
|
</div>
|
|
|
|
|
|
2025-12-01 22:57:12 +01:00
|
|
|
<div className="currency-display">
|
|
|
|
|
<div className="currency-badge">
|
|
|
|
|
<span className="currency-from">€1 EUR</span>
|
|
|
|
|
<span className="currency-arrow">→</span>
|
|
|
|
|
<span className="currency-to">NGN</span>
|
2024-01-14 16:30:19 +01:00
|
|
|
</div>
|
2025-12-01 22:57:12 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-12-01 22:23:08 +01:00
|
|
|
|
2025-12-01 22:57:12 +01:00
|
|
|
{loading && (
|
|
|
|
|
<div className="rates-grid">
|
|
|
|
|
{[...Array(6)].map((_, index) => (
|
|
|
|
|
<div key={index} className="rate-card skeleton">
|
|
|
|
|
<div className="skeleton-header"></div>
|
|
|
|
|
<div className="skeleton-rate"></div>
|
|
|
|
|
<div className="skeleton-badge"></div>
|
2025-12-01 22:23:08 +01:00
|
|
|
</div>
|
2025-12-01 22:57:12 +01:00
|
|
|
))}
|
2025-12-01 22:23:08 +01:00
|
|
|
</div>
|
2025-12-01 22:57:12 +01:00
|
|
|
)}
|
2025-12-01 22:23:08 +01:00
|
|
|
|
2025-12-01 22:57:12 +01:00
|
|
|
{error && (
|
|
|
|
|
<div className="error-state">
|
|
|
|
|
<div className="error-icon">⚠️</div>
|
|
|
|
|
<h3 className="error-title">Unable to load rates</h3>
|
|
|
|
|
<p className="error-message">{error}</p>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => window.location.reload()}
|
|
|
|
|
className="retry-button"
|
|
|
|
|
>
|
|
|
|
|
Try again
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{!loading && !error && sortedData && sortedData.length > 0 && (
|
|
|
|
|
<>
|
2025-12-01 22:23:08 +01:00
|
|
|
<div className="rates-grid">
|
2025-12-01 22:57:12 +01:00
|
|
|
{sortedData.map((item, index) => (
|
|
|
|
|
<RateCard
|
|
|
|
|
key={item.provider}
|
|
|
|
|
provider={item.provider}
|
|
|
|
|
rate={item.rate}
|
|
|
|
|
href={item.href}
|
|
|
|
|
isBest={item.bestRate || item.rate === bestRate}
|
|
|
|
|
index={index}
|
|
|
|
|
formatCurrency={formatCurrency}
|
|
|
|
|
/>
|
2025-12-01 22:23:08 +01:00
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-12-01 22:57:12 +01:00
|
|
|
<div className="rates-info">
|
|
|
|
|
<p className="info-text">
|
|
|
|
|
Rates are updated regularly. Click on a provider to visit their website.
|
|
|
|
|
</p>
|
|
|
|
|
<p className="info-timestamp">
|
|
|
|
|
Last updated: {new Date().toLocaleString('en-GB', {
|
|
|
|
|
dateStyle: 'medium',
|
|
|
|
|
timeStyle: 'short'
|
|
|
|
|
})}
|
2025-12-01 22:23:08 +01:00
|
|
|
</p>
|
|
|
|
|
</div>
|
2025-12-01 22:57:12 +01:00
|
|
|
</>
|
|
|
|
|
)}
|
2025-12-01 22:23:08 +01:00
|
|
|
|
2025-12-01 22:57:12 +01:00
|
|
|
{!loading && !error && (!data || data.length === 0) && (
|
|
|
|
|
<div className="empty-state">
|
|
|
|
|
<div className="empty-icon">📊</div>
|
|
|
|
|
<h3 className="empty-title">No rates available</h3>
|
|
|
|
|
<p className="empty-message">
|
|
|
|
|
We couldn't find any exchange rates at the moment.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</section>
|
|
|
|
|
</Layout>
|
2024-01-14 16:30:19 +01:00
|
|
|
);
|
|
|
|
|
}
|