chore: modularize ui components

This commit is contained in:
Jesse Egbosionu
2025-12-01 22:57:12 +01:00
parent 5c22b9b844
commit c460ea27d4
8 changed files with 270 additions and 216 deletions
+37 -81
View File
@@ -1,94 +1,50 @@
import Head from 'next/head';
import Layout from '../components/Layout';
import SocialLinks from '../components/SocialLinks';
export default function Home() {
return (
<div className="container">
<Head>
<title>Jesse Egbosionu - Software Engineer</title>
<link rel="icon" href="/favicon.ico" />
<meta name="description" content="Software engineer based in Amsterdam, focused on building scalable backend systems." />
</Head>
<Layout showNavLinks={false}>
<section className="hero">
<div className="hero-content">
<h1 className="hero-title">
Hey, I'm <span className="highlight">Jesse Egbosionu</span>
</h1>
<header className="header">
<nav className="navbar">
<div className="logo">
<span className="logo-text">JE</span>
</div>
<div className="nav-links">
<a href="mailto:me@jesseinit.dev" className="nav-link cta">Get in touch</a>
</div>
</nav>
</header>
<p className="hero-subtitle">
Software Engineer crafting scalable backend systems in Amsterdam
</p>
<main className="main-content">
<section className="hero">
<div className="hero-content">
<h1 className="hero-title">
Hey, I'm <span className="highlight">Jesse Egbosionu</span>
</h1>
<p className="hero-subtitle">
Software Engineer crafting scalable backend systems in Amsterdam
<div className="hero-description">
<p>
I build and scale software backends with a focus on creating impact through
teamwork and continuous learning. My passion lies in solving complex problems
and delivering solutions that enrich people's lives.
</p>
<div className="hero-description">
<p>
I build and scale software backends with a focus on creating impact through
teamwork and continuous learning. My passion lies in solving complex problems
and delivering solutions that enrich people's lives.
</p>
<p>
Over the past 6 years, I've shipped products across <strong>finance</strong>, <strong>ecommerce</strong>,
and <strong>logistics</strong>, working with technologies like Python (Django, FastAPI),
JavaScript (React, Node.js), PostgreSQL, Docker, Redis, and Kubernetes.
</p>
</div>
<div className="social-links">
<a href="https://github.com/jesseinit" target="_blank" rel="noopener noreferrer" className="social-link">
<svg className="social-icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
GitHub
</a>
<a href="https://linkedin.com/in/jesse-egbosionu/" target="_blank" rel="noopener noreferrer" className="social-link">
<svg className="social-icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z" />
</svg>
LinkedIn
</a>
<a href="https://twitter.com/jesseinit/" target="_blank" rel="noopener noreferrer" className="social-link">
<svg className="social-icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
Twitter
</a>
</div>
<a href="mailto:me@jesseinit.dev" className="primary-cta">
<span>Let's work together</span>
<span className="arrow">→</span>
</a>
<p>
Over the past 6 years, I've shipped products across <strong>finance</strong>, <strong>ecommerce</strong>,
and <strong>logistics</strong>, working with technologies like Python (Django, FastAPI),
JavaScript (React, Node.js), PostgreSQL, Docker, Redis, and Kubernetes.
</p>
</div>
<div className="hero-image-wrapper">
<div className="image-backdrop"></div>
<img
src="hero.jpg"
alt="Jesse Egbosionu"
className="hero-image"
/>
</div>
</section>
</main>
<SocialLinks />
<footer className="footer">
<div className="footer-content">
<p>Built with <span className="heart">❤️</span> and Amara Chukwu</p>
<p className="footer-year">© {new Date().getFullYear()}</p>
<a href="mailto:me@jesseinit.dev" className="primary-cta">
<span>Let's work together</span>
<span className="arrow">→</span>
</a>
</div>
</footer>
</div>
<div className="hero-image-wrapper">
<div className="image-backdrop"></div>
<img
src="hero.jpg"
alt="Jesse Egbosionu"
className="hero-image"
/>
</div>
</section>
</Layout>
);
}
+78 -118
View File
@@ -1,5 +1,6 @@
import Head from "next/head";
import { useEffect, useState } from "react";
import Layout from "../components/Layout";
import RateCard from "../components/RateCard";
import { fetchData } from "../utils/api";
export default function Rates() {
@@ -47,136 +48,95 @@ export default function Rates() {
}) : null;
return (
<div className="container">
<Head>
<title>EUR to NGN Exchange Rates - Jesse Egbosionu</title>
<link rel="icon" href="/favicon.ico" />
<meta name="description" content="Compare live EUR to NGN exchange rates across multiple providers." />
</Head>
<header className="header">
<nav className="navbar">
<div className="logo">
<a href="/">
<span className="logo-text">JE</span>
</a>
<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>
</div>
<div className="nav-links">
<a href="/" className="nav-link">Home</a>
<a href="/rates" className="nav-link cta">Rates</a>
</div>
</nav>
</header>
<main className="main-content">
<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>
<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>
</div>
</div>
</div>
<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>
{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>
</div>
</div>
))}
</div>
)}
{loading && (
{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 && (
<>
<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>
</div>
{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}
/>
))}
</div>
)}
{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 && (
<>
<div className="rates-grid">
{sortedData.map((item, index) => (
<a
key={item.provider}
href={item.href}
target="_blank"
rel="noopener noreferrer"
className={`rate-card ${item.bestRate || item.rate === bestRate ? 'best' : ''}`}
style={{ animationDelay: `${index * 0.1}s` }}
>
<div className="rate-card-header">
<h3 className="provider-name">{item.provider}</h3>
{(item.bestRate || item.rate === bestRate) && (
<span className="best-badge">Best Rate</span>
)}
</div>
<div className="rate-value">
{formatCurrency(item.rate)}
</div>
<div className="rate-card-footer">
<span className="visit-link">
Visit provider →
</span>
</div>
</a>
))}
</div>
<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'
})}
</p>
</div>
</>
)}
{!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.
<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'
})}
</p>
</div>
)}
</section>
</main>
</>
)}
<footer className="footer">
<div className="footer-content">
<p>Built with <span className="heart">❤️</span> and Amara Chukwu</p>
<p className="footer-year">© {new Date().getFullYear()}</p>
</div>
</footer>
</div>
{!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>
);
}