move front to folder

This commit is contained in:
Pablo Martin 2025-06-06 14:17:36 +02:00
parent 9a976a8be5
commit 793ff9fc96
14 changed files with 5 additions and 1 deletions

22
src/front/App.jsx Normal file
View file

@ -0,0 +1,22 @@
import AppHeader from "./components/AppHeader";
import AppSubHeader from "./components/AppSubHeader";
import LoanPanel from "./components/LoanPanel";
const App = () => {
return (
<div className="flex flex-col min-h-screen justify-center items-center ">
<div className="flex flex-col justify-center items-center my-5 text-white bg-blue-600 p-5 rounded-3xl shadow-[0_4px_0_rgba(0,255,255,1)]">
<div>
<AppHeader />
</div>
<div>
<AppSubHeader />
</div>
</div>
<LoanPanel />
<p className="mt-auto mb-5">Made by me, with lots of love</p>
</div>
);
};
export default App;

View file

@ -0,0 +1,5 @@
const Header = () => {
return <p className="text-3xl font-bold">Kuotata</p>;
};
export default Header;

View file

@ -0,0 +1,10 @@
const Subheader = ({ extraClassName }) => {
return (
<>
<p className={`text-xl`}>¿Jaqueca con tu préstamo?</p>
<p className={`text-xl`}>Calcula, compara y decide</p>
</>
);
};
export default Subheader;

View file

@ -0,0 +1,30 @@
const BaseInput = ({
label,
value,
onChangeCallback,
placeholder = "",
suffix,
inputWidth = "w-[60px]",
inputClassName = "",
}) => {
return (
<div className="flex md:flex-row flex-col items-center my-1">
<label className="" htmlFor="">
{label}
</label>
<div className="flex mx-auto">
<input
className={`md:ml-3 my-0 ${inputWidth} bg-white rounded-tl-md rounded-bl-md text-black text-right p-1 ${inputClassName}`}
value={value}
placeholder={placeholder}
onChange={onChangeCallback}
/>
<div className="w-[60px] place-self-center p-1 rounded-tr-md rounded-br bg-gray-600 font-light">
{suffix}
</div>
</div>
</div>
);
};
export default BaseInput;

View file

@ -0,0 +1,14 @@
import BaseInput from "./BaseInput";
const LoanDurationInput = ({ onChangeCallback, loanDuration }) => {
return (
<BaseInput
label="Duración"
value={loanDuration}
onChangeCallback={onChangeCallback}
suffix="Meses"
/>
);
};
export default LoanDurationInput;

View file

@ -0,0 +1,14 @@
import BaseInput from "./BaseInput";
const LoanInterestInput = ({ onChangeCallback, loanInterest }) => {
return (
<BaseInput
label="Interés (TIN)"
value={loanInterest}
onChangeCallback={onChangeCallback}
suffix="%"
/>
);
};
export default LoanInterestInput;

View file

@ -0,0 +1,88 @@
import { useState } from "react";
import LoanPrincipalInput from "./LoanPrincipalInput";
import LoanDurationInput from "./LoanDurationInput";
import LoanInterestInput from "./LoanInterestInput";
import sanitizeInputIntoRange from "../inputSanitizers/sanitizeInputIntoRange";
import sanitizeInputAsOnlyDigits from "../inputSanitizers/sanitizeInputAsOnlyDigits";
const LoanPanel = () => {
const [hasBeenInteracted, setHasBeenInteracted] = useState(false);
const [loanPrincipal, setLoanPrincipal] = useState("");
const [loanDuration, setLoanDuration] = useState(12);
const [loanInterest, setLoanInterest] = useState(5);
const sanitizeInputToIntWithinRange = ({ value, min, max }) => {
const onlyDigitsValue = sanitizeInputAsOnlyDigits({ value });
const inRangeValue = sanitizeInputIntoRange({
value: onlyDigitsValue,
min: min,
max: max,
});
return inRangeValue;
};
const handleLoanPrincipalChange = (event) => {
setHasBeenInteracted(1);
const inputValue = event.target.value;
const sanitizedInputValue = sanitizeInputToIntWithinRange({
value: inputValue,
min: 0,
max: 1_000_000,
});
setLoanPrincipal(sanitizedInputValue);
};
const handleLoanDurationChange = (event) => {
setHasBeenInteracted(1);
const inputValue = event.target.value;
const sanitizedInputValue = sanitizeInputToIntWithinRange({
value: inputValue,
min: 1,
max: 360,
});
setLoanDuration(sanitizedInputValue);
};
const handleLoanInterestChange = (event) => {
setHasBeenInteracted(1);
const inputValue = event.target.value;
const sanitizedInputValue = sanitizeInputToIntWithinRange({
value: inputValue,
min: 0,
max: 50,
});
setLoanInterest(sanitizedInputValue);
};
return (
<>
<div className=" mb p-5 bg-gray-50 rounded-3xl border border-gray-400">
<form className="flex flex-col justify-end md:items-end my-5 text-white bg-blue-600 p-5 rounded-3xl shadow-[0_4px_0_rgba(0,255,255,1)]">
<LoanPrincipalInput
onChangeCallback={handleLoanPrincipalChange}
loanPrincipal={loanPrincipal}
/>
<LoanDurationInput
onChangeCallback={handleLoanDurationChange}
loanDuration={loanDuration}
/>
<LoanInterestInput
onChangeCallback={handleLoanInterestChange}
loanInterest={loanInterest}
/>
</form>
{hasBeenInteracted ? (
<div className="flex flex-col items-center justify-center">
<div className="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
<p className="font-light">
Calculando los detalles del préstamo...
</p>
</div>
) : null}
</div>
</>
);
};
export default LoanPanel;

View file

@ -0,0 +1,17 @@
import BaseInput from "./BaseInput";
const LoanPrincipalInput = ({ onChangeCallback, loanPrincipal }) => {
return (
<BaseInput
label="Capital prestado"
value={loanPrincipal}
onChangeCallback={onChangeCallback}
suffix="€"
inputWidth="w-[120px]"
placeholder="1000"
inputClassName="!placeholder-gray-400"
/>
);
};
export default LoanPrincipalInput;

1
src/front/index.css Normal file
View file

@ -0,0 +1 @@
@import "tailwindcss";

13
src/front/index.html Normal file
View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.jsx"></script>
</body>
</html>

View file

@ -0,0 +1,6 @@
const sanitizeInputAsOnlyDigits = ({ value }) => {
const digitsOnly = value.replace(/\D/g, "");
return digitsOnly;
};
export default sanitizeInputAsOnlyDigits;

View file

@ -0,0 +1,15 @@
const sanitizeInputIntoRange = ({ value, min = null, max = null }) => {
const valueNumber = Number(value);
if (min && valueNumber < min) {
return min;
}
if (max && valueNumber > max) {
return max;
}
return valueNumber;
};
export default sanitizeInputIntoRange;

8
src/front/main.jsx Normal file
View file

@ -0,0 +1,8 @@
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
)