Skip to content

Commit

Permalink
frontend feat: complete react frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
Arabasta committed Jul 13, 2024
1 parent cff2681 commit 7b4d15d
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 18 deletions.
48 changes: 30 additions & 18 deletions react_frontend/src/App.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
import logo from './logo.svg';
import './App.css';
import React, { useState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import axios from 'axios';
import Form from './components/Form';
import Result from './components/Result';
import ErrorMessage from './components/ErrorMessage';
import Footer from "./components/footer";
import handleError from './utils/handleHttpError';

function App() {
const [result, setResult] = useState(null);
const [error, setError] = useState(null);

const handleSubmit = async (data) => {
setError(null);
setResult(null);

try {
const response = await axios.post(`${process.env.REACT_APP_API_URL}/api/v1/coin-change`, data);
setResult(response.data.coins);
} catch (err) {
setError(handleError(err));
}
};

return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
<div className="App container">
<h1 className="mt-5">Coin Change Kei Yam</h1>
<Form onSubmit={handleSubmit} />
{result && <Result coins={result} />}
{error && <ErrorMessage message={error} />}
<Footer />
</div>
);
}

Expand Down
15 changes: 15 additions & 0 deletions react_frontend/src/components/ErrorMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';

const ErrorMessage = ({ message }) => (
<div className="container mt-3">
<h2>Error</h2>
<p>{message}</p>
</div>
);

ErrorMessage.propTypes = {
message: PropTypes.string.isRequired,
};

export default ErrorMessage;
73 changes: 73 additions & 0 deletions react_frontend/src/components/Form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import 'bootstrap/dist/css/bootstrap.min.css';

const VALID_DENOMINATIONS = [0.01, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 50, 100, 1000];

const Form = ({ onSubmit }) => {
const [amount, setAmount] = useState('');
const [selectedDenominations, setSelectedDenominations] = useState([]);
const [validationError, setValidationError] = useState('');

const handleDenominationClick = (denomination) => {
setSelectedDenominations((prev) => {
if (prev.includes(denomination)) {
return prev.filter((d) => d !== denomination);
} else {
return [...prev, denomination];
}
});
};

const handleSubmit = (e) => {
e.preventDefault();
setValidationError('');

if (selectedDenominations.length === 0) {
setValidationError('Please select at least one denomination.');
return;
}

onSubmit({ amount: parseFloat(amount), denominations: selectedDenominations });
};

return (
<div className="container">
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>Amount:</label>
<input
type="number"
className="form-control"
value={amount}
onChange={(e) => setAmount(e.target.value)}
required
/>
</div>
<div className="form-group">
<label>Denominations:</label>
<div>
{VALID_DENOMINATIONS.map((denomination) => (
<button
key={denomination}
type="button"
className={`btn btn-outline-primary m-1 ${selectedDenominations.includes(denomination) ? 'active' : ''}`}
onClick={() => handleDenominationClick(denomination)}
>
{denomination}
</button>
))}
</div>
</div>
<button type="submit" className="btn btn-primary">Calculate</button>
</form>
{validationError && <p className="text-danger">{validationError}</p>}
</div>
);
};

Form.propTypes = {
onSubmit: PropTypes.func.isRequired,
};

export default Form;
15 changes: 15 additions & 0 deletions react_frontend/src/components/Result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';

const Result = ({ coins }) => (
<div className="container mt-3">
<h2>Result</h2>
<p>Coins: {coins.join(', ')}</p>
</div>
);

Result.propTypes = {
coins: PropTypes.arrayOf(PropTypes.number).isRequired,
};

export default Result;
21 changes: 21 additions & 0 deletions react_frontend/src/components/footer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';

const Footer = () => (
<footer className="bg-light text-center text-lg-start mt-5">
<div className="container p-4">
<p className="mb-0">Contact me:</p>
<ul className="list-unstyled">
<li><a href="mailto:[email protected]">[email protected]</a></li>
<li><a href="https://keiyam.me" target="_blank" rel="noopener noreferrer">Personal Website</a>
</li>
<li><a href="https://github.com/arabasta" target="_blank" rel="noopener noreferrer">Github</a>
</li>
<li><a href="https://www.linkedin.com/in/kyi" target="_blank" rel="noopener noreferrer">LinkedIn</a>
</li>
<li>+65 88008405</li>
</ul>
</div>
</footer>
);

export default Footer;
19 changes: 19 additions & 0 deletions react_frontend/src/utils/handleHttpError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const handleError = (err) => {
if (err.response) {
if (err.response.status === 400) {
if (typeof err.response.data === 'string') {
return err.response.data || 'Invalid input data. Please check your values.';
} else if (typeof err.response.data === 'object') {
return Object.values(err.response.data).join(', ') || 'Invalid input data. Please check your values.';
} else {
return 'Invalid input data. Please check your values.';
}
} else {
return 'An error occurred on the server.';
}
} else {
return 'Failed to connect to the server.';
}
};

export default handleError;

0 comments on commit 7b4d15d

Please sign in to comment.