-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
24 changed files
with
7,132 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# See https://help.github.com/ignore-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
|
||
# testing | ||
/coverage | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
.env.local | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
/.idea | ||
|
||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"name": "ppcalc", | ||
"version": "0.1.0", | ||
"private": true, | ||
"dependencies": { | ||
"axios": "^0.16.2", | ||
"react": "^15.6.1", | ||
"react-dom": "^15.6.1", | ||
"react-loading": "^0.1.4", | ||
"react-select": "^1.0.0-rc.5" | ||
}, | ||
"devDependencies": { | ||
"react-scripts": "1.0.7" | ||
}, | ||
"scripts": { | ||
"start": "react-scripts start", | ||
"build": "react-scripts build", | ||
"test": "react-scripts test --env=jsdom", | ||
"eject": "react-scripts eject", | ||
"deploy": "npm run build && cd build && mv index.html 200.html && surge . --domain ppfeecalc.surge.sh" | ||
} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
<meta name="theme-color" content="#000000"> | ||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"> | ||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> | ||
<title>PayPal Fee Calculator</title> | ||
</head> | ||
<body> | ||
<noscript> | ||
You need to enable JavaScript to run this app. | ||
</noscript> | ||
<div id="root"></div> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"short_name": "PayPalFeeCalc", | ||
"name": "PayPal Fee Calculator", | ||
"icons": [ | ||
{ | ||
"src": "favicon.ico", | ||
"sizes": "192x192", | ||
"type": "image/png" | ||
} | ||
], | ||
"start_url": "./index.html", | ||
"display": "standalone", | ||
"theme_color": "#000000", | ||
"background_color": "#ffffff" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import React from 'react'; | ||
import './css/App.css'; | ||
import './css/bootstrap/bootstrap.min.css'; | ||
import 'react-select/dist/react-select.css'; | ||
|
||
import Calculator from "./components/Calculator"; | ||
|
||
class App extends React.Component { | ||
render() { | ||
return ( | ||
<div className="container" style={{paddingTop: 100}}> | ||
<Calculator/> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import App from './App'; | ||
|
||
it('renders without crashing', () => { | ||
const div = document.createElement('div'); | ||
ReactDOM.render(<App />, div); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
import React from 'react'; | ||
|
||
import Header from './Header'; | ||
import Options from "./Options"; | ||
import Results from "./Results"; | ||
|
||
import CurrencyConversion from "../http/CurrencyConversion" | ||
import fees from '../data/fees' | ||
|
||
// PayPal adds ~2.5% on top of the market exchange rates when converting currencies. | ||
const INTL_FEE = 2.5; | ||
|
||
class Calculator extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
|
||
this.state = { | ||
amount: null, | ||
results: null, | ||
loading: false, | ||
feeFixed: 0.30, | ||
feePercent: 2.9, | ||
customFee: false, | ||
currencyFrom: "USD", | ||
currencyTo: "USD", | ||
rate: 1 | ||
}; | ||
|
||
this.updateAmount = this.updateAmount.bind(this); | ||
this.updateFromCurrency = this.updateFromCurrency.bind(this); | ||
this.updateToCurrency = this.updateToCurrency.bind(this); | ||
this.updateCustomPercent = this.updateCustomPercent.bind(this); | ||
this.updateSelectedCountry = this.updateSelectedCountry.bind(this); | ||
} | ||
|
||
askFee(amount, feePercent, feeFixed, needsConversion) { | ||
feePercent = needsConversion ? feePercent + INTL_FEE : feePercent; | ||
let r = amount * (feePercent / 100) + feeFixed; | ||
let i = amount - r; | ||
return { | ||
fee: r.toFixed(2), | ||
value: i.toFixed(2) | ||
} | ||
} | ||
|
||
wantFee(amount, feePercent, feeFixed, needsConversion) { | ||
feePercent = needsConversion ? feePercent + INTL_FEE : feePercent; | ||
let r = (100 - feePercent) / 100; | ||
let i = (amount + feeFixed) / r; | ||
let s = i - amount; | ||
return { | ||
fee: s.toFixed(2), | ||
value: i.toFixed(2) | ||
} | ||
} | ||
|
||
calculateFees(amount) { | ||
if (!amount) { | ||
return; | ||
} | ||
|
||
let rate = this.state.rate; | ||
let needsConversion = rate !== 1; | ||
|
||
|
||
let fees = { | ||
ask: this.askFee(amount, this.state.feePercent, this.state.feeFixed / rate, needsConversion), | ||
want: this.wantFee(amount, this.state.feePercent, this.state.feeFixed, needsConversion) | ||
}; | ||
|
||
if (rate !== 1) { | ||
fees.ask.raw = fees.ask.fee; | ||
fees.want.raw = (fees.want.fee / rate).toFixed(2); | ||
fees.ask.value = (fees.ask.value * rate).toFixed(2); | ||
fees.ask.fee = (fees.ask.fee * rate).toFixed(2); | ||
fees.want.value = (fees.want.value / rate).toFixed(2); | ||
} | ||
return fees; | ||
} | ||
|
||
updateSelectedCountry(event) { | ||
this.updateThenCalculate({ | ||
feePercent: event.value | ||
}); | ||
this.setState({ | ||
customFee: false | ||
}); | ||
} | ||
|
||
updateFromCurrency(event) { | ||
this.setState({ | ||
currencyFrom: event.value | ||
}); | ||
this.refreshRates(event.value, this.state.currencyTo) | ||
} | ||
|
||
updateToCurrency(event) { | ||
this.setState({ | ||
currencyTo: event.value, | ||
feeFixed: fees.fixed[event.value] | ||
}); | ||
this.refreshRates(this.state.currencyFrom, event.value) | ||
} | ||
|
||
refreshRates(from, to) { | ||
this.setState({ | ||
loading: true | ||
}); | ||
|
||
CurrencyConversion.rates(from, to).then((rate) => { | ||
this.updateThenCalculate({ | ||
rate, | ||
loading: false | ||
}); | ||
}); | ||
} | ||
|
||
updateThenCalculate(newState) { | ||
new Promise((resolve) => { | ||
resolve(this.setState(newState)); | ||
}).then(() => { | ||
this.setState({ | ||
results: this.calculateFees(this.state.amount) | ||
}); | ||
}); | ||
} | ||
|
||
updateCustomPercent(event) { | ||
let val = event.target.value ? event.target.value : 0; | ||
|
||
if (val < 0 || val > 15) | ||
{ | ||
this.setState({ | ||
feePercent: this.state.feePercent | ||
}); | ||
return; | ||
} | ||
|
||
this.updateThenCalculate({ | ||
feePercent: parseFloat(val), | ||
customFee: true | ||
}); | ||
} | ||
|
||
updateAmount(event) { | ||
let amount = parseFloat(event.target.value); | ||
|
||
if (!amount || amount <= 0) { | ||
this.setState({ | ||
amount: null, | ||
results: null | ||
}); | ||
return; | ||
} | ||
|
||
this.setState({ | ||
amount, | ||
results: this.calculateFees(amount) | ||
}); | ||
} | ||
|
||
render() { | ||
return ( | ||
<div className="row"> | ||
<div className="col-md-8 col-md-offset-2"> | ||
<Header | ||
updateAmount={this.updateAmount} | ||
feePercent={this.state.feePercent} | ||
feeFixed={this.state.feeFixed} | ||
currencyTo={this.state.currencyTo} | ||
fxFee={this.state.rate !== 1} | ||
/> | ||
|
||
<Options | ||
updateSelectedCountry={this.updateSelectedCountry} | ||
updateCustomPercent={this.updateCustomPercent} | ||
updateFromCurrency={this.updateFromCurrency} | ||
updateToCurrency={this.updateToCurrency} | ||
feePercent={this.state.feePercent} | ||
customFee={this.state.customFee} | ||
feeFixed={this.state.feeFixed} | ||
currencyTo={this.state.currencyTo} | ||
currencyFrom={this.state.currencyFrom} | ||
/> | ||
|
||
<Results | ||
currencyTo={this.state.currencyTo} | ||
currencyFrom={this.state.currencyFrom} | ||
results={this.state.results} | ||
loading={this.state.loading} | ||
amount={this.state.amount} | ||
fxFee={this.state.rate !== 1} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default Calculator; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import React from 'react'; | ||
|
||
function Header(props) { | ||
return ( | ||
<div className="panel panel-default"> | ||
<div className="panel-heading text-center"> | ||
<h4>PayPal Fee Calculator</h4> | ||
</div> | ||
<div className="panel-body"> | ||
<div className="form-group col-md-6"> | ||
<input type="number" className="form-control" placeholder="Amount in $" | ||
onChange={props.updateAmount}/> | ||
</div> | ||
<div className="form-group col-md-6"> | ||
<input type="number" className="form-control text-center" readOnly | ||
placeholder={`${props.feePercent}% + ${props.fxFee ? '2.5% +': ''} ${props.feeFixed} ${props.currencyTo}`}/> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default Header; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import React from 'react'; | ||
import Select from 'react-select'; | ||
|
||
import fees from '../data/fees' | ||
import currency from '../data/currency' | ||
|
||
function Options(props) { | ||
return ( | ||
<div className="panel panel-default"> | ||
<div className="panel-body"> | ||
<div> | ||
<div className="row"> | ||
<div className="form-group col-md-7"> | ||
<label>Select your Country</label> | ||
<Select | ||
clearable={false} | ||
placeholder={`Using a custom fee of ${props.feePercent}%`} | ||
onChange={props.updateSelectedCountry} | ||
value={props.feePercent} | ||
options={fees.countries} | ||
/> | ||
</div> | ||
<div className="col-md-1 text-center"> | ||
<br /> | ||
<h4><strong>Or</strong></h4> | ||
</div> | ||
<div className="form-group col-md-4"> | ||
<label>Custom Percentage Fee</label> | ||
<input type="number" min="1" max="10" step ="0.01" className="form-control" placeholder="2.9%" | ||
onChange={props.updateCustomPercent} value={props.customFee ? props.feePercent : ''} style={{height: 36}}/> | ||
</div> | ||
</div> | ||
<div className="row"> | ||
<div className="form-group col-md-6"> | ||
<label>Currency From</label> | ||
<Select | ||
clearable={false} | ||
onChange={props.updateFromCurrency} | ||
value={props.currencyFrom} | ||
options={currency.symbols} | ||
/> | ||
</div> | ||
<div className="form-group col-md-6"> | ||
<label>Currency To</label> | ||
<Select | ||
clearable={false} | ||
onChange={props.updateToCurrency} | ||
value={props.currencyTo} | ||
options={currency.symbols} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default Options; |
Oops, something went wrong.