diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..30db35e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+# Node and React
+node_modules/
+dist/
+node/
+coverage/
+build/
+out/
+**/yarn.lock
+**/stats.json
+package-lock.json
+
+# Eclipse, Netbeans, vsCode and IntelliJ files
+/.*
+!.github
+!.gitignore
+/nbproject
+/*.ipr
+/*.iws
+.idea
+**/*.iml
+.classpath
+.project
+.settings
+**/junit.xml
+.vscode/
+
+# Mac
+**/.DS_Store
+
+# Maven
+log/
+target/
+
+# Other
+*.log*
+tmp/
+bin/
+local/
\ No newline at end of file
diff --git a/clean-frontend.sh b/clean-frontend.sh
new file mode 100755
index 0000000..b6c39c0
--- /dev/null
+++ b/clean-frontend.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+find . -type d -name 'node_modules' -prune -exec rm -rf {} \;
+find . -type d -name 'node' -prune -exec rm -rf {} \;
+find . -type d -name 'build' -prune -exec rm -rf {} \;
+find . -type f -name 'package-lock.json' -prune -exec rm -rf {} \;
+find . -type f -name 'tsconfig.json' -prune -exec rm -rf {} \;
+find . -type f -name '*.log' -prune -exec rm -rf {} \;
\ No newline at end of file
diff --git a/packages/qlb-react-app/README.md b/packages/qlb-react-app/README.md
new file mode 100644
index 0000000..92abefd
--- /dev/null
+++ b/packages/qlb-react-app/README.md
@@ -0,0 +1,57 @@
+# QLB React App
+
+Frontend application for the Quick Loan Bank demo,
+This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app), and beautified with [Patternfly](https://www.patternfly.org/v4/)
+
+## Quick start
+
+In the project directory, you can run:
+
+```bash
+npm install && npm run start
+```
+
+## Development Scripts
+
+Runs the app in the development mode.
+
+```bash
+# Install development/build dependencies
+npm install
+
+# Start the development server
+npm run start
+```
+
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. The page will reload if you make edits.
+You will also see any lint errors in the console.
+
+## Production Scripts
+
+Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance.
+The build is minified and the filenames include the hashes.
+
+```bash
+npm run build
+```
+
+Your app is now ready to be deployed!
+
+For environments using `Node`, the easiest way to handle deployment would be to install `serve` and let it handle the rest:
+
+```bash
+npm install -g serve
+```
+
+The following command will serve your static site on the port `5000` (default).
+
+```bash
+serve -s build
+```
+
+The port can be adjusted using the `-l` or `--listen` flags "
+
+```bash
+serve -s build -l 4000
+```
diff --git a/packages/qlb-react-app/package.json b/packages/qlb-react-app/package.json
new file mode 100644
index 0000000..7f87889
--- /dev/null
+++ b/packages/qlb-react-app/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "qlb-react-app",
+ "version": "0.1.0",
+ "description": "",
+ "dependencies": {
+ "@patternfly/react-core": "^3.153.13",
+ "@patternfly/react-icons": "^3.15.16",
+ "@testing-library/jest-dom": "^4.2.4",
+ "@testing-library/react": "^9.5.0",
+ "@testing-library/user-event": "^7.2.1",
+ "@types/react-router-dom": "^5.1.5",
+ "axios": "^0.19.2",
+ "react": "^16.13.1",
+ "react-axe": "^3.4.1",
+ "react-dom": "^16.13.1",
+ "react-router-dom": "^5.1.2",
+ "react-router-last-location": "^2.0.1",
+ "react-scripts": "3.4.1"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "eslintConfig": {
+ "extends": "react-app"
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "@types/react": "^16.9.34",
+ "@types/react-dom": "^16.9.7",
+ "source-map-loader": "^0.2.4",
+ "ts-loader": "^7.0.2",
+ "typescript": "^3.8.3"
+ }
+}
diff --git a/packages/qlb-react-app/public/favicon.ico b/packages/qlb-react-app/public/favicon.ico
new file mode 100644
index 0000000..da87a81
Binary files /dev/null and b/packages/qlb-react-app/public/favicon.ico differ
diff --git a/packages/qlb-react-app/public/index.html b/packages/qlb-react-app/public/index.html
new file mode 100644
index 0000000..490057e
--- /dev/null
+++ b/packages/qlb-react-app/public/index.html
@@ -0,0 +1,42 @@
+
+
+
+
+ Pre-Approval App
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/qlb-react-app/public/loanDurationOptions.json b/packages/qlb-react-app/public/loanDurationOptions.json
new file mode 100644
index 0000000..50f8808
--- /dev/null
+++ b/packages/qlb-react-app/public/loanDurationOptions.json
@@ -0,0 +1,42 @@
+[
+ {
+ "value": "please choose",
+ "label": "Please Choose",
+ "disabled": true
+ },
+ {
+ "value": 5,
+ "label": "5 years",
+ "disabled": false
+ },
+ {
+ "value": 7,
+ "label": "7 years",
+ "disabled": false
+ },
+ {
+ "value": 10,
+ "label": "10 years",
+ "disabled": false
+ },
+ {
+ "value": 12,
+ "label": "12 years",
+ "disabled": false
+ },
+ {
+ "value": 15,
+ "label": "15 years",
+ "disabled": false
+ },
+ {
+ "value": 20,
+ "label": "20 years",
+ "disabled": false
+ },
+ {
+ "value": 25,
+ "label": "25 years",
+ "disabled": false
+ }
+]
\ No newline at end of file
diff --git a/packages/qlb-react-app/public/logo192.png b/packages/qlb-react-app/public/logo192.png
new file mode 100644
index 0000000..fe88bc3
Binary files /dev/null and b/packages/qlb-react-app/public/logo192.png differ
diff --git a/packages/qlb-react-app/public/logo512.png b/packages/qlb-react-app/public/logo512.png
new file mode 100644
index 0000000..31ffc29
Binary files /dev/null and b/packages/qlb-react-app/public/logo512.png differ
diff --git a/packages/qlb-react-app/public/manifest.json b/packages/qlb-react-app/public/manifest.json
new file mode 100644
index 0000000..080d6c7
--- /dev/null
+++ b/packages/qlb-react-app/public/manifest.json
@@ -0,0 +1,25 @@
+{
+ "short_name": "React App",
+ "name": "Create React App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ },
+ {
+ "src": "logo192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "logo512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/packages/qlb-react-app/public/qlb_logo.png b/packages/qlb-react-app/public/qlb_logo.png
new file mode 100644
index 0000000..a3056c5
Binary files /dev/null and b/packages/qlb-react-app/public/qlb_logo.png differ
diff --git a/packages/qlb-react-app/public/robots.txt b/packages/qlb-react-app/public/robots.txt
new file mode 100644
index 0000000..e9e57dc
--- /dev/null
+++ b/packages/qlb-react-app/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/packages/qlb-react-app/src/actions.js b/packages/qlb-react-app/src/actions.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/qlb-react-app/src/components/App.tsx b/packages/qlb-react-app/src/components/App.tsx
new file mode 100644
index 0000000..dcc7d14
--- /dev/null
+++ b/packages/qlb-react-app/src/components/App.tsx
@@ -0,0 +1,13 @@
+import * as React from 'react';
+import '@patternfly/react-core/dist/styles/base.css';
+import { BrowserRouter as Router } from 'react-router-dom';
+import { AppLayout } from './AppLayout';
+import '../css/App.css';
+
+const App: React.FunctionComponent = () => (
+
+
+
+);
+
+export { App };
\ No newline at end of file
diff --git a/packages/qlb-react-app/src/components/AppLayout.tsx b/packages/qlb-react-app/src/components/AppLayout.tsx
new file mode 100644
index 0000000..e35c270
--- /dev/null
+++ b/packages/qlb-react-app/src/components/AppLayout.tsx
@@ -0,0 +1,127 @@
+import * as React from 'react';
+import {NavLink, Route, RouteComponentProps, Switch} from 'react-router-dom';
+import {
+ Nav,
+ NavList,
+ NavItem,
+ NavVariants,
+ Page,
+ PageHeader,
+ PageSidebar,
+ SkipToContent
+} from '@patternfly/react-core';
+import BrandComponent from './BrandComponent';
+import PageToolbarComponent from './PageToolbarComponent';
+import {useDocumentTitle} from "./utils/useDocumentTitle";
+import {NotFound} from "./notFound/NotFound";
+import {Home} from "./home/Home";
+import {PreApproval} from "./preApproval/PreApproval";
+
+export interface IAppRoute {
+ label?: string;
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ component: React.ComponentType> | React.ComponentType;
+ /* eslint-enable @typescript-eslint/no-explicit-any */
+ exact?: boolean;
+ path: string;
+ title: string;
+ isAsync?: boolean;
+}
+
+const routes: IAppRoute[] = [
+ {
+ component: Home,
+ exact: true,
+ label: 'Home',
+ path: '/',
+ title: 'Quick Loan Bank | Home'
+ },
+ {
+ component: PreApproval,
+ exact: true,
+ isAsync: true,
+ label: 'Pre-approval',
+ path: '/pre-approval',
+ title: 'Quick Loan Bank | Pre-approval'
+ }
+];
+
+
+const AppLayout: React.FC<{}> = (props: any) => {
+ const logoProps = {
+ href: '/',
+ target: '_blank'
+ };
+ const [isNavOpen, setIsNavOpen] = React.useState(true);
+ const [isMobileView, setIsMobileView] = React.useState(true);
+ const [isNavOpenMobile, setIsNavOpenMobile] = React.useState(false);
+ const onNavToggleMobile = () => {
+ setIsNavOpenMobile(!isNavOpenMobile);
+ };
+ const onNavToggle = () => {
+ setIsNavOpen(!isNavOpen);
+ }
+ const onPageResize = (props: { mobileView: boolean; windowSize: number }) => {
+ setIsMobileView(props.mobileView);
+ };
+ const Header = (
+ }
+ logoProps={logoProps}
+ toolbar={}
+ showNavToggle
+ isNavOpen={isNavOpen}
+ onNavToggle={isMobileView ? onNavToggleMobile : onNavToggle}
+ />
+ );
+
+ const Navigation = (
+
+ );
+ const Sidebar = (
+
+ );
+ const PageSkipToContent = (
+
+ Skip to Content
+
+ );
+ const PageNotFound = ({ title }: { title: string }) => {
+ useDocumentTitle(title);
+ return ;
+ };
+ return (
+
+
+ {routes.map(({ path, exact, component, title, isAsync }, idx) => (
+
+ ))}
+
+
+
+ );
+}
+
+export { AppLayout };
\ No newline at end of file
diff --git a/packages/qlb-react-app/src/components/BrandComponent.tsx b/packages/qlb-react-app/src/components/BrandComponent.tsx
new file mode 100644
index 0000000..184fe69
--- /dev/null
+++ b/packages/qlb-react-app/src/components/BrandComponent.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { Brand } from '@patternfly/react-core';
+import { withRouter } from 'react-router-dom';
+import { RouteComponentProps } from 'react-router';
+import qlbLogo from '../static/qlb_logo.png';
+
+const BrandComponent: React.FC = ({ history }) => {
+ const onLogoClick = () => {
+ history.push('/');
+ };
+ return (
+
+ );
+};
+
+export default withRouter(BrandComponent);
\ No newline at end of file
diff --git a/packages/qlb-react-app/src/components/PageToolbarComponent.tsx b/packages/qlb-react-app/src/components/PageToolbarComponent.tsx
new file mode 100644
index 0000000..dee76ea
--- /dev/null
+++ b/packages/qlb-react-app/src/components/PageToolbarComponent.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import {
+ Dropdown,
+ DropdownToggle,
+ Toolbar,
+ ToolbarGroup,
+ ToolbarItem
+} from '@patternfly/react-core';
+import accessibleStyles from '@patternfly/react-styles/css/utilities/Accessibility/accessibility';
+import { css } from '@patternfly/react-styles';
+
+const PageToolbarComponent: React.FunctionComponent = () => {
+ let userName = 'Anonymous';
+
+ return (
+
+
+
+
+
+ {userName}
+
+ }
+ />
+
+
+
+
+ );
+};
+
+export default PageToolbarComponent;
\ No newline at end of file
diff --git a/packages/qlb-react-app/src/components/home/Home.tsx b/packages/qlb-react-app/src/components/home/Home.tsx
new file mode 100644
index 0000000..d191eca
--- /dev/null
+++ b/packages/qlb-react-app/src/components/home/Home.tsx
@@ -0,0 +1,13 @@
+import * as React from 'react';
+import {Brand, Bullseye, PageSection} from '@patternfly/react-core';
+import qlbLogo from '../../static/qlb_logo_w.png';
+
+const Home: React.FunctionComponent<{}> = () => (
+
+
+
+
+
+)
+
+export {Home};
\ No newline at end of file
diff --git a/packages/qlb-react-app/src/components/notFound/NotFound.tsx b/packages/qlb-react-app/src/components/notFound/NotFound.tsx
new file mode 100644
index 0000000..d13ca67
--- /dev/null
+++ b/packages/qlb-react-app/src/components/notFound/NotFound.tsx
@@ -0,0 +1,12 @@
+import * as React from 'react';
+import { NavLink } from 'react-router-dom';
+import { Alert, PageSection } from '@patternfly/react-core';
+
+const NotFound: React.FunctionComponent = () => (
+
+
+ Take me home
+
+ )
+
+export { NotFound };
diff --git a/packages/qlb-react-app/src/components/preApproval/PreApproval.tsx b/packages/qlb-react-app/src/components/preApproval/PreApproval.tsx
new file mode 100644
index 0000000..e4509d5
--- /dev/null
+++ b/packages/qlb-react-app/src/components/preApproval/PreApproval.tsx
@@ -0,0 +1,10 @@
+import * as React from 'react';
+import { PageSection } from '@patternfly/react-core';
+import PreApprovalCheckForm from './PreApprovalCheckForm';
+const PreApproval: React.FunctionComponent<{}> = () => (
+
+
+
+)
+
+export { PreApproval };
\ No newline at end of file
diff --git a/packages/qlb-react-app/src/components/preApproval/PreApprovalCheckForm.js b/packages/qlb-react-app/src/components/preApproval/PreApprovalCheckForm.js
new file mode 100644
index 0000000..91775dd
--- /dev/null
+++ b/packages/qlb-react-app/src/components/preApproval/PreApprovalCheckForm.js
@@ -0,0 +1,348 @@
+import React from "react";
+import {
+ Bullseye,
+ Stack,
+ StackItem,
+ Form,
+ Title,
+ FormGroup,
+ TextInput,
+ ActionGroup,
+ FormSelect,
+ FormSelectOption,
+ Button,
+ Alert,
+ AlertGroup,
+ AlertVariant,
+ EmptyState,
+ EmptyStateVariant,
+ EmptyStateBody,
+ Divider,
+ Card,
+ CardHeader,
+ CardBody,
+} from "@patternfly/react-core";
+import axios from "axios";
+
+class PreApprovalCheckForm extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ applicantName: "",
+ applicantAge: "",
+ applicantMonthlyIncome: "",
+ applicantCreditScore: "",
+ loanAmount: "",
+ loanDuration: "",
+ result: "",
+ rejectionReasons: [],
+ interestRate: "",
+ checked: false,
+ isValidAge: false,
+ isValidIncome: false,
+ isValidCreditScore: false,
+ isValidAmount: false,
+ isValidDuration: false,
+ };
+ this.handleApplicantName = (applicantName) => {
+ this.setState({ applicantName });
+ };
+ this.handleApplicantAge = (applicantAge) => {
+ this.setState({
+ applicantAge: Number(applicantAge),
+ isValidAge: /^([1-9][0-9]*)/.test(applicantAge),
+ });
+ };
+ this.handleApplicantMonthlyIncome = (applicantMonthlyIncome) => {
+ this.setState({
+ applicantMonthlyIncome: Number(applicantMonthlyIncome),
+ isValidIncome: /^([1-9][0-9]*)/.test(applicantMonthlyIncome),
+ });
+ };
+ this.handleApplicantCreditScore = (applicantCreditScore) => {
+ this.setState({
+ applicantCreditScore: Number(applicantCreditScore),
+ isValidCreditScore: /^\d{3}$/.test(applicantCreditScore),
+ });
+ };
+ this.handleLoanAmount = (loanAmount) => {
+ this.setState({
+ loanAmount: Number(loanAmount),
+ isValidAmount: /^([1-9][0-9]*)/.test(loanAmount),
+ });
+ };
+ this.handleLoanDuration = (loanDuration) => {
+ this.setState({
+ loanDuration: Number(loanDuration),
+ isValidDuration: loanDuration !== "",
+ });
+ };
+
+ this.options = [
+ { value: "", label: "Please Choose", disabled: true },
+ { value: 5, label: "5 years", disabled: false },
+ { value: 7, label: "7 years", disabled: false },
+ { value: 10, label: "10 years", disabled: false },
+ { value: 12, label: "12 years", disabled: false },
+ { value: 15, label: "15 years", disabled: false },
+ { value: 20, label: "20 years", disabled: false },
+ { value: 25, label: "25 years", disabled: false },
+ ];
+ this.handleCheckPreApproval = this.handleCheckPreApproval.bind(this);
+ }
+
+ handleCheckPreApproval(e) {
+ e.preventDefault();
+ if (!e.target.checkValidity()) {
+ console.log("form is invalid!!");
+ return;
+ }
+ console.log(this.state);
+ axios
+ .post(`http://localhost:8081/loan-preapproval`, {
+ Applicant: {
+ "Monthly Income": this.state.applicantMonthlyIncome,
+ "Credit Score": this.state.applicantCreditScore,
+ Name: this.state.applicantName,
+ Age: this.state.applicantAge,
+ },
+ Loan: {
+ Amount: this.state.loanAmount,
+ Duration: this.state.loanDuration,
+ },
+ })
+ .then((res) => {
+ // console.log(res);
+ // console.log(res.data);
+ // console.log(res.data["Pre Approval"]["Rejection Reasons"]);
+ this.setState({
+ result: res.data["Pre Approval"].Result,
+ interestRate: res.data["Interest Rate"],
+ rejectionReasons: res.data["Pre Approval"]["Rejection Reasons"],
+ checked: true,
+ });
+ })
+ .catch((err) => {
+ console.log(err);
+ });
+ }
+
+ render() {
+ const {
+ applicantName,
+ applicantAge,
+ applicantMonthlyIncome,
+ applicantCreditScore,
+ loanAmount,
+ loanDuration,
+ result,
+ rejectionReasons,
+ interestRate,
+ checked,
+ isValidAge,
+ isValidIncome,
+ isValidCreditScore,
+ isValidAmount,
+ isValidDuration,
+ } = this.state;
+
+ let alert;
+ if (checked) {
+ if (result) {
+ var title = "Pre-approved with an interest rate of " + interestRate;
+ alert = (
+
+
+
+ Result
+
+
+
+
+
+
+
+
+
+ );
+ } else {
+ alert = (
+
+
+
+ Result
+
+
+
+
+
+
+ {rejectionReasons.map((reason, id) => (
+
+ ))}
+
+
+
+ );
+ }
+ }
+
+ return (
+
+
+
+
+ Loan Pre-Approval Check
+
+
+
+
+
+ {alert}
+
+
+ QLB Bank Demo
+
+
+
+
+ );
+ }
+}
+
+export default PreApprovalCheckForm;
diff --git a/packages/qlb-react-app/src/components/utils/useDocumentTitle.ts b/packages/qlb-react-app/src/components/utils/useDocumentTitle.ts
new file mode 100644
index 0000000..0442ab4
--- /dev/null
+++ b/packages/qlb-react-app/src/components/utils/useDocumentTitle.ts
@@ -0,0 +1,13 @@
+import * as React from 'react';
+
+// a custom hook for setting the page title
+export function useDocumentTitle(title: string) {
+ React.useEffect(() => {
+ const originalTitle = document.title;
+ document.title = title;
+
+ return () => {
+ document.title = originalTitle;
+ };
+ }, [title]);
+}
diff --git a/packages/qlb-react-app/src/constants.js b/packages/qlb-react-app/src/constants.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/qlb-react-app/src/css/App.css b/packages/qlb-react-app/src/css/App.css
new file mode 100644
index 0000000..47a749b
--- /dev/null
+++ b/packages/qlb-react-app/src/css/App.css
@@ -0,0 +1,3 @@
+html, body, #root {
+ height: 100%;
+}
\ No newline at end of file
diff --git a/packages/qlb-react-app/src/index.tsx b/packages/qlb-react-app/src/index.tsx
new file mode 100644
index 0000000..51ffa87
--- /dev/null
+++ b/packages/qlb-react-app/src/index.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import { App } from "./components/App";
+
+if (process.env.NODE_ENV !== "production") {
+ const config = {
+ rules: [
+ {
+ id: "color-contrast",
+ enabled: false,
+ },
+ ],
+ };
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, no-undef
+ const axe = require("react-axe");
+ axe(React, ReactDOM, 1000, config);
+}
+
+ReactDOM.render(, document.getElementById("root") as HTMLElement);
diff --git a/packages/qlb-react-app/src/initialState.json b/packages/qlb-react-app/src/initialState.json
new file mode 100644
index 0000000..b65e8f9
--- /dev/null
+++ b/packages/qlb-react-app/src/initialState.json
@@ -0,0 +1,21 @@
+{
+ "applicant": {
+ "monthlyIncome": 4000,
+ "creditScore": 300,
+ "name": "Lucien Bramard",
+ "age": 16
+ },
+ "loan": {
+ "amount": 300000,
+ "duration": 20
+ },
+ "preApproval": {
+ "rejectionReasons": [
+ "Non eligible age",
+ "High debt ratio (31.68%)",
+ "Insufficient credit score (min required is 500)"
+ ],
+ "result": false
+ },
+ "interestRate": 1.39
+}
\ No newline at end of file
diff --git a/packages/qlb-react-app/src/react-app-env.d.ts b/packages/qlb-react-app/src/react-app-env.d.ts
new file mode 100644
index 0000000..6431bc5
--- /dev/null
+++ b/packages/qlb-react-app/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/packages/qlb-react-app/src/static/KogitoAbout.png b/packages/qlb-react-app/src/static/KogitoAbout.png
new file mode 100644
index 0000000..3a26234
Binary files /dev/null and b/packages/qlb-react-app/src/static/KogitoAbout.png differ
diff --git a/packages/qlb-react-app/src/static/qlb_logo.png b/packages/qlb-react-app/src/static/qlb_logo.png
new file mode 100644
index 0000000..eaf40b5
Binary files /dev/null and b/packages/qlb-react-app/src/static/qlb_logo.png differ
diff --git a/packages/qlb-react-app/src/static/qlb_logo_w.png b/packages/qlb-react-app/src/static/qlb_logo_w.png
new file mode 100644
index 0000000..af2d77e
Binary files /dev/null and b/packages/qlb-react-app/src/static/qlb_logo_w.png differ
diff --git a/packages/qlb-react-app/src/typings.d.ts b/packages/qlb-react-app/src/typings.d.ts
new file mode 100644
index 0000000..16df0f5
--- /dev/null
+++ b/packages/qlb-react-app/src/typings.d.ts
@@ -0,0 +1,12 @@
+declare module '*.png';
+declare module '*.jpg';
+declare module '*.jpeg';
+declare module '*.gif';
+declare module '*.svg';
+declare module '*.css';
+declare module '*.wav';
+declare module '*.mp3';
+declare module '*.m4a';
+declare module '*.rdf';
+declare module '*.ttl';
+declare module '*.pdf';
diff --git a/qlb-ui/README.md b/qlb-ui/README.md
new file mode 100644
index 0000000..c4e8c0e
--- /dev/null
+++ b/qlb-ui/README.md
@@ -0,0 +1,74 @@
+# QLB UI
+
+This project use Vert.x and Quarkus, to render a React Application
+
+If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ .
+
+## Running the application in dev mode
+
+You can run your application in dev mode that enables live coding using:
+
+```
+mvn quarkus:dev
+```
+
+Note: Live coding of the React JS frontend application is not yet in place.
+
+## Packaging and running the application
+
+The application is packageable using:
+
+```
+mvn package
+```
+
+It produces the executable `qlb-ui-1.0-runner.jar` file in `/target` directory.
+Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/lib` directory.
+
+The application is now runnable using:
+
+```
+java -jar target/qlb-ui-1.0-runner.jar
+```
+
+## Creating a native executable
+
+You can create a native executable using:
+
+```
+mvn package -Dnative
+```
+
+Or you can use Docker to build the native executable using:
+
+```
+mvn package -Dnative -Dquarkus.native.container-build=true
+```
+
+You can then execute your binary: `./target/qlb-ui-1.0-runner.jar`
+
+If you want to learn more about building native executables, please consult https://quarkus.io/guides/building-native-image-guide .
+
+## Packaging together with the React app
+
+The application makes use of a separately developed [QLB React App](../packages/qlb-react-app/package.json). The JS based frontend can be built as part of the build of this project by using the `ui` profile, invoked using
+
+```
+mvn package -Dui
+```
+
+To prepare all the dependencies needed for the build of UI, there's a maven profile activated by `ui.deps` property being specified that user need to run before the actual build.
+
+The single command to activate both profiles is:
+
+```
+mvn package -Dui.deps -Dui
+```
+
+## Creating a native executable
+
+The native build of the application bundling in the React JS frontend does not differ from the instructions above. The only thing that's new is again the invocation of UI specific profiles.
+
+```
+mvn package -Dui -Dnative
+```
diff --git a/qlb-ui/pom.xml b/qlb-ui/pom.xml
new file mode 100644
index 0000000..96702b7
--- /dev/null
+++ b/qlb-ui/pom.xml
@@ -0,0 +1,251 @@
+
+
+
+
+ 4.0.0
+
+ Quick Loan Bank :: User interface
+
+ com.redhat.demo.qlb
+ qlb-ui
+ 1.0
+
+ User interface for the qlb demo.
+
+
+ ../packages/qlb-react-app
+ 1.4.1.Final
+ 0.10.1
+ v11.6.0
+ 6.10.3
+ v1.19.1
+ 1.9.1
+ 2.22.1
+ 3.8.1
+ 3.1.0
+ UTF-8
+ 11
+
+
+
+
+
+
+ org.kie.kogito
+ kogito-bom
+ ${kogito.version}
+ pom
+ import
+
+
+ io.quarkus
+ quarkus-bom
+ ${quarkus.version}
+ pom
+ import
+
+
+
+
+
+
+ io.quarkus
+ quarkus-vertx-web
+
+
+ io.quarkus
+ quarkus-resteasy
+
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${compiler.plugin.version}
+
+ ${maven.compiler.release}
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+ ${quarkus.version}
+
+ true
+
+
+
+
+ build
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${surefire.plugin.version}
+
+
+ org.jboss.logmanager.LogManager
+
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+ ${version.frontend.plugin}
+
+ ${path.to.frontend.app}
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ ${resources.plugin.version}
+
+
+ copy-resources
+ process-resources
+
+ copy-resources
+
+
+ ${basedir}/target/classes/META-INF/resources/
+
+
+ ${path.to.frontend.app}/build
+ false
+
+
+
+
+
+
+
+
+
+
+ native
+
+
+ native
+
+
+
+
+
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+
+ ${project.build.directory}/${project.build.finalName}-runner
+
+
+
+
+
+
+
+
+ native
+
+
+
+
+ Install node and yarn
+
+ true
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+
+
+ install-node-and-npm
+
+ install-node-and-npm
+
+
+ ${version.node}
+ ${version.npm}
+
+
+
+ npm install
+
+ npm
+
+
+
+ install
+
+
+
+
+
+
+
+
+ Build the UI
+
+ true
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+
+
+ npm run build
+
+ npm
+
+
+
+ run build
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/qlb-ui/src/main/java/com/redhat/demo/qlb/VertxRouter.java b/qlb-ui/src/main/java/com/redhat/demo/qlb/VertxRouter.java
new file mode 100644
index 0000000..27428d7
--- /dev/null
+++ b/qlb-ui/src/main/java/com/redhat/demo/qlb/VertxRouter.java
@@ -0,0 +1,61 @@
+package com.redhat.demo.qlb;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Observes;
+import javax.inject.Inject;
+
+import io.quarkus.vertx.web.Route;
+import io.vertx.core.Vertx;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.handler.StaticHandler;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static io.vertx.core.http.HttpMethod.GET;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+@ApplicationScoped
+public class VertxRouter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(VertxRouter.class);
+
+ // @Inject
+ // @ConfigProperty(name = "preapproval.service.http.url", defaultValue =
+ // "http://localhost:8081")
+
+ @Inject
+ Vertx vertx;
+
+ private String resource;
+
+ @PostConstruct
+ public void init() {
+ try {
+ resource = vertx.fileSystem().readFileBlocking("META-INF/resources/index.html").toString(UTF_8);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ void setupRouter(@Observes Router router) {
+ router.route("/").handler(ctx -> handle(ctx));
+ router.route("/pre-approval").handler(ctx -> handle(ctx));
+ router.route().handler(StaticHandler.create());
+ }
+
+ public void handle(RoutingContext context) {
+ try {
+ context.response().putHeader(HttpHeaders.CACHE_CONTROL, "no-cache")
+ .putHeader(HttpHeaders.CONTENT_TYPE, "text/html;charset=utf8").end(resource);
+ } catch (Exception ex) {
+ LOGGER.error("Error handling index.html", ex);
+ context.fail(500, ex);
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/qlb-ui/src/main/resources/application.properties b/qlb-ui/src/main/resources/application.properties
new file mode 100644
index 0000000..9478b5d
--- /dev/null
+++ b/qlb-ui/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+quarkus.http.port=8082
+# preapproval.service.http.url="http://localhost:8081"
\ No newline at end of file
diff --git a/qlb-ui/src/test/java/com/redhat/demo/qlb/StaticContentTest.java b/qlb-ui/src/test/java/com/redhat/demo/qlb/StaticContentTest.java
new file mode 100644
index 0000000..d020c7b
--- /dev/null
+++ b/qlb-ui/src/test/java/com/redhat/demo/qlb/StaticContentTest.java
@@ -0,0 +1,38 @@
+package com.redhat.demo.qlb;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+
+import io.quarkus.test.common.http.TestHTTPResource;
+import io.quarkus.test.junit.QuarkusTest;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@QuarkusTest
+public class StaticContentTest {
+
+ @TestHTTPResource("index.html")
+ URL url;
+
+ private static String readStream(InputStream in) throws IOException {
+ byte[] data = new byte[1024];
+ int r;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ while ((r = in.read(data)) > 0) {
+ out.write(data, 0, r);
+ }
+ return new String(out.toByteArray(), StandardCharsets.UTF_8);
+ }
+
+ @Test
+ public void testIndexHtml() throws Exception {
+ try (InputStream in = url.openStream()) {
+ String contents = readStream(in);
+ assertTrue(contents.contains("Pre-Approval App"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/qlb-ui/src/test/java/com/redhat/demo/qlb/VertxRouterTest.java b/qlb-ui/src/test/java/com/redhat/demo/qlb/VertxRouterTest.java
new file mode 100644
index 0000000..bd7328c
--- /dev/null
+++ b/qlb-ui/src/test/java/com/redhat/demo/qlb/VertxRouterTest.java
@@ -0,0 +1,19 @@
+package com.redhat.demo.qlb;
+
+import io.quarkus.test.junit.QuarkusTest;
+import org.junit.jupiter.api.Test;
+
+import static io.restassured.RestAssured.given;
+
+@QuarkusTest
+public class VertxRouterTest {
+
+ @Test
+ public void testHandlePath() {
+ given().when().get("/").then().statusCode(200);
+
+ given().when().get("/pre-approval").then().statusCode(200);
+
+ given().when().get("/Another").then().statusCode(404);
+ }
+}
\ No newline at end of file
diff --git a/qlb-ui/src/test/resources/application.properties b/qlb-ui/src/test/resources/application.properties
new file mode 100644
index 0000000..b3ea95c
--- /dev/null
+++ b/qlb-ui/src/test/resources/application.properties
@@ -0,0 +1 @@
+quarkus.http.test-port=8083
\ No newline at end of file