Commit 0699eb77 authored by Bilal's avatar Bilal

Added form to create invoice

parent b76681d3
...@@ -11,8 +11,11 @@ ...@@ -11,8 +11,11 @@
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.3.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-redux": "^8.1.3",
"react-router-dom": "^6.19.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
} }
...@@ -3261,6 +3264,24 @@ ...@@ -3261,6 +3264,24 @@
} }
} }
}, },
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@remix-run/router": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.12.0.tgz",
"integrity": "sha512-2hXv036Bux90e1GXTWSMfNzfDDK8LA8JYEWfyHxzvwdp6GyoWEovKc9cotb3KCKmkdwsIBuFGX7ScTWyiHv7Eg==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@rollup/plugin-babel": { "node_modules/@rollup/plugin-babel": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
...@@ -4026,6 +4047,15 @@ ...@@ -4026,6 +4047,15 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/html-minifier-terser": { "node_modules/@types/html-minifier-terser": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
...@@ -4456,6 +4486,11 @@ ...@@ -4456,6 +4486,11 @@
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.6.tgz",
"integrity": "sha512-HYtNooPvUY9WAVRBr4u+4Qa9fYD1ze2IUlAD3HoA6oehn1taGwBx3Oa52U4mTslTS+GAExKpaFu39Y5xUEwfjg==" "integrity": "sha512-HYtNooPvUY9WAVRBr4u+4Qa9fYD1ze2IUlAD3HoA6oehn1taGwBx3Oa52U4mTslTS+GAExKpaFu39Y5xUEwfjg=="
}, },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
},
"node_modules/@types/ws": { "node_modules/@types/ws": {
"version": "8.5.9", "version": "8.5.9",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz",
...@@ -5726,6 +5761,24 @@ ...@@ -5726,6 +5761,24 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
}, },
"node_modules/bootstrap": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz",
"integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
...@@ -8940,6 +8993,19 @@ ...@@ -8940,6 +8993,19 @@
"he": "bin/he" "he": "bin/he"
} }
}, },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/hoist-non-react-statics/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/hoopy": { "node_modules/hoopy": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
...@@ -14717,6 +14783,49 @@ ...@@ -14717,6 +14783,49 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
}, },
"node_modules/react-redux": {
"version": "8.1.3",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz",
"integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==",
"dependencies": {
"@babel/runtime": "^7.12.1",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/use-sync-external-store": "^0.0.3",
"hoist-non-react-statics": "^3.3.2",
"react-is": "^18.0.0",
"use-sync-external-store": "^1.0.0"
},
"peerDependencies": {
"@types/react": "^16.8 || ^17.0 || ^18.0",
"@types/react-dom": "^16.8 || ^17.0 || ^18.0",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0",
"react-native": ">=0.59",
"redux": "^4 || ^5.0.0-beta.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
},
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-redux/node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"node_modules/react-refresh": { "node_modules/react-refresh": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
...@@ -14725,6 +14834,36 @@ ...@@ -14725,6 +14834,36 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-router": {
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.19.0.tgz",
"integrity": "sha512-0W63PKCZ7+OuQd7Tm+RbkI8kCLmn4GPjDbX61tWljPxWgqTKlEpeQUwPkT1DRjYhF8KSihK0hQpmhU4uxVMcdw==",
"dependencies": {
"@remix-run/router": "1.12.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.19.0.tgz",
"integrity": "sha512-N6dWlcgL2w0U5HZUUqU2wlmOrSb3ighJmtQ438SWbhB1yuLTXQ8yyTBMK3BSvVjp7gBtKurT554nCtMOgxCZmQ==",
"dependencies": {
"@remix-run/router": "1.12.0",
"react-router": "6.19.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/react-scripts": { "node_modules/react-scripts": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
...@@ -16871,6 +17010,14 @@ ...@@ -16871,6 +17010,14 @@
"requires-port": "^1.0.0" "requires-port": "^1.0.0"
} }
}, },
"node_modules/use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
......
.App { .App {
text-align: center; text-align: center;
font-family: sans-serif
} }
.App-logo { .App-logo {
height: 40vmin; height: 100px;
width: 100px;
pointer-events: none; pointer-events: none;
margin-top: 43px;
}
.logo-back {
background: #9898f2;
} }
@media (prefers-reduced-motion: no-preference) { @media (prefers-reduced-motion: no-preference) {
...@@ -14,14 +21,9 @@ ...@@ -14,14 +21,9 @@
} }
.App-header { .App-header {
background-color: #282c34;
min-height: 100vh;
display: flex; display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin); font-size: calc(10px + 2vmin);
color: white; margin-top: 43px
} }
.App-link { .App-link {
...@@ -36,3 +38,23 @@ ...@@ -36,3 +38,23 @@
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
@media only screen and (max-width: 768px) {
.d-small {
display: block;
}
.d-large {
display: none;
}
}
@media only screen and (min-width: 768px) {
.d-small {
display: none;
}
.d-large {
display: block;
}
}
import logo from './logo.svg'; import logo from './ivrnet.png';
import TelepayForm from './component/Telepay';
import './App.css'; import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
function App() { function App() {
return ( return (
<div className="App"> <div className='App'>
<header className="App-header"> <div className='row'>
<img src={logo} className="App-logo" alt="logo" /> <div className='d-large position-fixed col-md-2 h-100 logo-back'>
<p> <div className='mb-2'>
Edit <code>src/App.js</code> and save to reload. <img src={logo} className='App-logo' alt='logo' />
</p> </div>
<a <h3 className='text-white'>Ivrnet</h3>
className="App-link" </div>
href="https://reactjs.org"
target="_blank" <div className='d-small'>
rel="noopener noreferrer" <div className='mb-2'>
> <img src={logo} className='App-logo' alt='logo' />
Learn React </div>
</a> <h3 className='text-white'>Ivrnet</h3>
</header> </div>
<div className='offset-md-2 col-md-8'>
<header className='mb-4 mt-4'>
<h3 className='text-center'>Payment Details</h3>
</header>
<TelepayForm />
</div>
</div>
</div> </div>
); );
} }
......
.add-product {
margin-left: 74%;
margin-top: -5%;
margin-bottom: 2%;
}
.next-btn {
margin-top: -6%;
}
.form-label {
font-weight: bold;
}
import React, { useState } from 'react';
import './Pay.css';
const TelepayForm = () => {
const [error, setError] = useState(null);
const [success, setSuccess] = useState(null);
const [formData, setFormData] = useState({
first_name: '',
last_name: '',
address: '',
city: '',
province: '',
country: '',
email: '',
phone: '',
description: '',
line_items: [
{ description: '', amount: '' },
],
});
const handleChange = (field, value) => {
setFormData({
...formData,
[field]: value,
});
};
const handleProductChange = (index, field, value) => {
const updatedProducts = [...formData.line_items];
updatedProducts[index][field] = value;
setFormData({
...formData,
line_items: updatedProducts,
});
};
const handleAddProduct = () => {
setFormData({
...formData,
line_items: [...formData.line_items, { description: '', amount: '' }],
});
};
const handleSubmit = async (e) => {
e.preventDefault();
try {
const basicAuthHeader = 'Basic ' + btoa('test23:password');
const response = await fetch('https://central-staging.ivrnet.com/api/v1/invoices/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': basicAuthHeader,
},
body: JSON.stringify(formData),
});
if (response.ok) {
const responseData = await response.json();
setSuccess(responseData);
} else {
const errorData = await response.json();
setError(errorData.error || 'An error occurred');
window.scroll({ top: 0, left: 0, behavior: 'smooth' });
}
} catch (error) {
setError('An error occurred while making the API request');
window.scroll({ top: 0, left: 0, behavior: 'smooth' });
}
};
const handleDeleteLineItem = (index) => {
const updatedLineItems = [...formData.line_items];
updatedLineItems.splice(index, 1);
setFormData({
...formData,
line_items: updatedLineItems,
});
};
return (
<div>
{error && (
<div className='alert alert-danger' role='alert'>
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div className='form-group row mb-4'>
<div className='col'>
<label htmlFor='firstName' className='form-label mt-1'>First Name</label>
</div>
<div className='col-md-9'>
<input
placeholder='First name'
className='inputContainer form-control'
value={formData.first_name}
onChange={(e) => handleChange('firstName', e.target.value)}
/>
</div>
</div>
<div className='form-group row mb-4'>
<div className='col'>
<label htmlFor='LastName' className='form-label mt-1'>Last Name</label>
</div>
<div className='col-md-9'>
<input
placeholder='Last name'
className='inputContainer form-control'
value={formData.last_name}
onChange={(e) => handleChange('lastName', e.target.value)}
/>
</div>
</div>
<div className='form-group row mb-4'>
<div className='col'>
<label htmlFor='Address' className='form-label mt-1'>Address</label>
</div>
<div className='col-md-9'>
<input
placeholder='Address'
className='inputContainer form-control'
value={formData.address}
onChange={(e) => handleChange('address', e.target.value)}
/>
</div>
</div>
<div className='form-group row mb-4'>
<div className='col'>
<label htmlFor='City' className='form-label mt-1'>City</label>
</div>
<div className='col-md-9'>
<input
placeholder='City'
className='inputContainer form-control'
value={formData.city}
onChange={(e) => handleChange('city', e.target.value)}
/>
</div>
</div>
<div className='form-group row mb-4'>
<div className='col'>
<label htmlFor='Province' className='form-label mt-1'>Province</label>
</div>
<div className='col-md-9'>
<input
placeholder='Province'
className='inputContainer form-control'
value={formData.province}
onChange={(e) => handleChange('province', e.target.value)}
/>
</div>
</div>
<div className='form-group row mb-4'>
<div className='col'>
<label htmlFor='Country' className='form-label mt-1'>Country</label>
</div>
<div className='col-md-9'>
<input
placeholder='Country'
className='inputContainer form-control'
value={formData.country}
onChange={(e) => handleChange('country', e.target.value)}
/>
</div>
</div>
<div className='form-group row mb-4'>
<div className='col'>
<label htmlFor='Email' className='form-label mt-1'>Email</label>
</div>
<div className='col-md-9'>
<input
placeholder='Email'
className='inputContainer form-control'
value={formData.email}
type='email'
onChange={(e) => handleChange('email', e.target.value)}
/>
</div>
</div>
<div className='form-group row mb-4'>
<div className='col'>
<label htmlFor='Phone' className='form-label mt-1'>Phone</label>
</div>
<div className='col-md-9'>
<input
placeholder='Phone number'
className='inputContainer form-control'
value={formData.phone}
onChange={(e) => handleChange('phone', e.target.value)}
/>
</div>
</div>
<div className='form-group row mb-4'>
<div className='col'>
<label htmlFor='Description' className='form-label mt-1'>Description</label>
</div>
<div className='col-md-9'>
<input
placeholder='Enter description'
className='inputContainer form-control'
value={formData.description}
onChange={(e) => handleChange('description', e.target.value)}
required
/>
</div>
</div>
<h3 className='text-center'>Line Items</h3>
<div className='form-group row mb-4'>
<div className='col-md-3'>
</div>
<div className='col-md-12'>
{formData.line_items.map((line_items, index) => (
<div key={index}>
<div className='row mb-4' id={`lineItem-${index}`}>
<div className='col-5 offset-1'>
<label htmlFor={`lineItemDes-${index}`} className='form-label mt-1'>
Description
</label>
<input
placeholder='Description'
value={line_items.name}
id={`lineItemDes-${index}`}
className='inputContainer form-control'
onChange={(e) => handleProductChange(index, 'description', e.target.value)}
required
/>
</div>
<div className='col-5'>
<label htmlFor={`lineItemAmount-${index}`} className='form-label mt-1'>
Amount
</label>
<input
placeholder='Enter Price'
type='number'
value={line_items.amount}
id={`lineItemAmount-${index}`}
className='inputContainer form-control'
onChange={(e) => handleProductChange(index, 'amount', e.target.value)}
required
/>
</div>
<div className='col'>
<button type='button' className='btn-close' aria-label='Close'
onClick={() => handleDeleteLineItem(index)}></button>
</div>
</div>
</div>
))}
</div>
<div className='row'>
<div className='col-12 pe-0'>
<button onClick={handleAddProduct} type='button' className='btn btn-link float-end'>
Add Line Item
</button>
</div>
</div>
</div>
{success && (
<div className='alert alert-primary' role='alert'>
Access Code: {success.access_code}
</div>
)}
<div className='form-group'>
<button type='submit' className='btn btn-outline-primary'>
Submit
</button>
</div>
</form>
</div>
);
};
export default TelepayForm;
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment