Skip to content
This repository has been archived by the owner on Nov 26, 2024. It is now read-only.

Commit

Permalink
feat(exchange-rate): exchange rate page
Browse files Browse the repository at this point in the history
  • Loading branch information
cedricpoon committed Oct 24, 2024
1 parent ff3e676 commit 27b61ad
Show file tree
Hide file tree
Showing 19 changed files with 461 additions and 59 deletions.
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@testing-library/user-event": "^13.5.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-flag-kit": "^1.1.1",
"react-redux": "^9.1.2",
"react-router-dom": "^6.27.0",
"react-scripts": "^5.0.1",
Expand All @@ -34,7 +35,7 @@
},
"scripts": {
"start": "craco start",
"build": "craco build",
"build": "cross-env REACT_APP_COMMIT_HASH=$(git rev-parse HEAD) craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
Expand Down
20 changes: 15 additions & 5 deletions public/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,22 @@ import { StaleWhileRevalidate, CacheFirst } from 'workbox-strategies';
import { precacheAndRoute } from 'workbox-precaching';

// Precache all the assets generated by the build process.
precacheAndRoute(self.__WB_MANIFEST || []);
precacheAndRoute(self.__WB_MANIFEST || [], {
maximumFileSizeToCacheInBytes: 50 * 1024 * 1024, // 50 MB
});

// Cache API requests
// Cache exchange rate API requests
registerRoute(
({ url }) => url.origin === 'https://open.er-api.com',
new StaleWhileRevalidate({
cacheName: 'exchange-rates-cache',
plugins: [
// Optionally, add expiration or other plugins
],
})
);

// Cache other API requests as needed
registerRoute(
({ url }) => url.origin === 'https://cedio.github.io', // Update with your API domain if different
new StaleWhileRevalidate({
Expand All @@ -19,9 +32,6 @@ registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'image-cache',
plugins: [
// Add plugins if needed
],
})
);

Expand Down
10 changes: 9 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import React from 'react';
import React, { useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import Transactions from './pages/Transactions';
import Participants from './pages/Participants';
import Balance from './pages/Balance';
import Navbar from './components/Navbar';
import { useDispatch } from 'react-redux';
import { fetchExchangeRates } from './redux/slices/currencySlice';

function App() {
const dispatch = useDispatch();

useEffect(() => {
dispatch(fetchExchangeRates());
}, [dispatch]);

return (
<Router basename="/pay-as-we-go">
<Navbar />
Expand Down
77 changes: 77 additions & 0 deletions src/components/ExchangeRates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Typography, Box, Grid, Paper, Avatar, Button } from '@mui/material';
import { setSelectedCurrency, fetchExchangeRates } from '../redux/slices/currencySlice';
import { FlagIcon } from 'react-flag-kit';
import currencyToCountry from '../utils/currencyToCountry';
import useOnlineStatus from '../hooks/useOnlineStatus';

function ExchangeRates() {
const dispatch = useDispatch();
const { exchangeRates, availableCurrencies, selectedCurrency, lastUpdated, status } = useSelector((state) => state.currency);
const isOnline = useOnlineStatus();

const handleRefresh = () => {
if (isOnline) {
dispatch(fetchExchangeRates());
} else {
alert('Cannot refresh exchange rates while offline.');
}
};

return (
<Box sx={{ mt: 4 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="h5">Current Exchange Rates</Typography>
<Button variant="contained" onClick={handleRefresh} disabled={!isOnline}>
Refresh Rates
</Button>
</Box>
<Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
Last Updated: {lastUpdated || 'N/A'}
</Typography>
<Paper elevation={3} sx={{ p: 2 }}>
<Grid container spacing={2}>
{availableCurrencies.map((currency) => {
const countryCode = currencyToCountry[currency];
if (!countryCode) {
console.warn(`No country code found for currency: ${currency}`);
} else {
console.log(`Currency: ${currency}, Country Code: ${countryCode}`);
}
return (
<Grid item xs={12} sm={6} md={4} key={currency}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{countryCode ? (
<Avatar sx={{ mr: 2, bgcolor: 'transparent' }}>
<FlagIcon code={countryCode} size={24} />
</Avatar>
) : (
<Avatar sx={{ mr: 2, bgcolor: 'transparent' }}>
{/* Optionally, use a default icon or omit the avatar */}
<span>🏳️</span>
</Avatar>
)}
<Typography variant="body1" sx={{ flexGrow: 1 }}>
{currency}: {exchangeRates[currency]}
</Typography>
{selectedCurrency !== currency && (
<Button
size="small"
variant="outlined"
onClick={() => dispatch(setSelectedCurrency(currency))}
>
Select
</Button>
)}
</Box>
</Grid>
);
})}
</Grid>
</Paper>
</Box>
);
}

export default ExchangeRates;
16 changes: 16 additions & 0 deletions src/components/Metadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import { Typography, Box } from '@mui/material';

function Metadata() {
const commitHash = process.env.REACT_APP_COMMIT_HASH || 'N/A';

return (
<Box sx={{ mt: 2, mb: 2 }}>
<Typography variant="body2" color="textSecondary">
Latest Commit: {commitHash.substring(0, 7)}
</Typography>
</Box>
);
}

export default Metadata;
39 changes: 38 additions & 1 deletion src/components/Navbar.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import React from 'react';
import { AppBar, Toolbar, Typography, Button, IconButton, Drawer, List, ListItem, ListItemText, useTheme, useMediaQuery } from '@mui/material';
import { AppBar, Toolbar, Typography, Button, IconButton, Drawer, List, ListItem, ListItemText, useTheme, useMediaQuery, Select, MenuItem } from '@mui/material';
import { Menu as MenuIcon } from '@mui/icons-material';
import { Link } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import { setSelectedCurrency } from '../redux/slices/currencySlice';

function Navbar() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const [drawerOpen, setDrawerOpen] = React.useState(false);
const dispatch = useDispatch();
const { availableCurrencies, selectedCurrency } = useSelector((state) => state.currency);

const handleDrawerToggle = () => {
setDrawerOpen(!drawerOpen);
};

const handleCurrencyChange = (event) => {
dispatch(setSelectedCurrency(event.target.value));
};

const menuItems = [
{ text: 'Home', path: '/' },
{ text: 'Participants', path: '/participants' },
Expand All @@ -27,6 +35,20 @@ function Navbar() {
<ListItemText primary={item.text} />
</ListItem>
))}
<ListItem>
<Select
value={selectedCurrency}
onChange={handleCurrencyChange}
displayEmpty
fullWidth
>
{availableCurrencies.map((currency) => (
<MenuItem key={currency} value={currency}>
{currency}
</MenuItem>
))}
</Select>
</ListItem>
</List>
</div>
);
Expand All @@ -49,6 +71,21 @@ function Navbar() {
{item.text}
</Button>
))}
{!isMobile && (
<Select
value={selectedCurrency}
onChange={handleCurrencyChange}
variant="outlined"
size="small"
style={{ marginLeft: '1rem', color: 'white', borderColor: 'white' }}
>
{availableCurrencies.map((currency) => (
<MenuItem key={currency} value={currency}>
{currency}
</MenuItem>
))}
</Select>
)}
</Toolbar>
</AppBar>
<Drawer anchor="left" open={drawerOpen} onClose={handleDrawerToggle}>
Expand Down
24 changes: 24 additions & 0 deletions src/hooks/useOnlineStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useState, useEffect } from 'react';

function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);

const updateOnlineStatus = () => {
setIsOnline(navigator.onLine);
};

useEffect(() => {
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);

// Cleanup
return () => {
window.removeEventListener('online', updateOnlineStatus);
window.removeEventListener('offline', updateOnlineStatus);
};
}, []);

return isOnline;
}

export default useOnlineStatus;
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ ReactDOM.render(
document.getElementById('root')
);

// Register the service worker for offline capabilities
serviceWorkerRegistration.register();
Loading

0 comments on commit 27b61ad

Please sign in to comment.