Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*

.vscode
1 change: 0 additions & 1 deletion .stylelintrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"extends": "stylelint-config-standard",
"rules": {
"indentation": "tab",
"string-quotes": "double",
Expand Down
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"Freshbooks"
]
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
You can see hte project live here: [Invoice Free](https://invoicefee.app/).
You can see the project live here: [Invoice Free](https://invoicefee.app/).

## Tech used:

Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,10 @@
"not dead",
"not ie <= 11",
"not op_mini all"
]
],
"devDependencies": {
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"typescript": "^4.1.3"
}
}
2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<!-- OG -->
<!-- COMMON TAGS -->
<meta charset="utf-8" />
<title>Freelance Fee Calculator</title>
<title>Invoice Fee Calculator</title>
<!-- Search Engine -->
<meta
name="description"
Expand Down
102 changes: 0 additions & 102 deletions src/App.js

This file was deleted.

100 changes: 100 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { Component, FormEvent, useState } from "react";
import Form from "./components/Form";
import TotalAmount from "./components/TotalAmount";
import AffiliateLink from "./components/AffiliateLink";
import SuggestedAmount from "./components/SuggestedAmount";

import "./App.css";
import { NumberFormatValues } from "react-number-format";

export type TReverseCut = (amount: number | null) => number;

function App() {
const [isAffiliateOpen, setIsAffiliateOpen] = useState(false);
const [originalAmount, setOriginalAmount] = useState<number | null>(null);
const [freshbooksCut, setFreshbooksCut] = useState<number | null>(null);
const [submit, setSubmit] = useState<"success" | "error" | null>(null);

function handleInput({
formattedValue,
value,
floatValue,
}: NumberFormatValues) {
floatValue && setOriginalAmount(floatValue);
setFreshbooksCut(freshbooksCut);
setSubmit(null);
}

function handleFreshbooksCut(amount: string | number): number {
if (typeof amount == "number") {
return Number((amount * 0.029 + 0.3).toFixed(2));
} else {
return Number((Number(amount) * 0.029 + 0.3).toFixed(2));
}
}

const reverseCut: TReverseCut = (amount: number | null) => {
const newAmount = (Number(amount) / 0.971 + 0.3).toFixed(2);
return Number(newAmount);
};

function toggleAffiliate() {
setIsAffiliateOpen(!isAffiliateOpen);
}

function calculateAmount(event: FormEvent) {
event.preventDefault();
const amountNumber = Number(originalAmount);

if (amountNumber && amountNumber > 0) {
setFreshbooksCut(handleFreshbooksCut(amountNumber));
setSubmit("success");
} else {
setSubmit("error");
setFreshbooksCut(0);
setOriginalAmount(0);
}
}

return (
<div className="app">
<h1 className="app-title">Invoice Fee Calculator</h1>
<p className="app-subtitle">
Calculate how much money will end up in your pocket after Freshbooks'
fees.
</p>
<Form
calculateAmount={calculateAmount}
handleInput={handleInput}
amountValue={originalAmount}
/>
{submit === `success` && (
<TotalAmount
freshbooksCut={freshbooksCut}
originalAmount={originalAmount}
/>
)}
{submit === `success` && (
<SuggestedAmount
originalAmount={originalAmount}
reverseCut={reverseCut}
/>
)}
z
{submit === "error" && (
<p className="notification notification--error">
Please enter a valid value
</p>
)}
{/*<HowItWorks />*/}
<AffiliateLink
isAffiliateOpen={isAffiliateOpen}
toggleAffiliate={toggleAffiliate}
/>
<p className="site-credit">
Site by <a href="https://adamrasheed.com">Adam Rasheed</a>
</p>
</div>
);
}
export default App;
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import React from "react";
import Logo from "../freshbooks-logo.png";

const AffiliateLink = ({ link, affilateOpen, affiliateToggle }) => {
const affLink = 'https://www.shareasale.com/r.cfm?u=1246035&m=52946&b=593723'

type AffiliateLinkProps = {
link?: string;
isAffiliateOpen: boolean;
toggleAffiliate: () => void
}

function AffiliateLink ({ link = affLink, isAffiliateOpen, toggleAffiliate}: AffiliateLinkProps) {
return (
<div className="freshbooks-link">
<a
Expand Down Expand Up @@ -32,20 +40,20 @@ const AffiliateLink = ({ link, affilateOpen, affiliateToggle }) => {
Get a FREE Trial
</a>
<button
onClick={affiliateToggle}
onClick={toggleAffiliate}
className="freshbooks-link__disclaimer small center"
>
Affiliate Disclosure
</button>
{affilateOpen && (
{isAffiliateOpen && (
<p className="affiliate-disclosure">
The FreshBooks button above is an affilate link, which means that if
you sign up for a freetrial and/or a paid subscription I might receive
some money at no additional cost to you.
</p>
)}
</div>
);
};
)
}

export default AffiliateLink;
26 changes: 0 additions & 26 deletions src/components/Form.js

This file was deleted.

31 changes: 31 additions & 0 deletions src/components/Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from "react";
import NumberFormat, { NumberFormatValues } from "react-number-format";

type FormProps = {
calculateAmount: any;
handleInput: (values: NumberFormatValues) => void;
amountValue: any;
};

function Form({ calculateAmount, handleInput, amountValue }: FormProps) {
return (
<form className="form" action="" onSubmit={calculateAmount}>
<label htmlFor="amountInput" className="form__label">
Amount ($)
</label>
<NumberFormat
thousandSeparator={true}
prefix="$"
className="form__input"
id="amountInput"
onValueChange={handleInput}
value={amountValue}
/>
<button className="form__submit" type="submit">
Calculate Fees
</button>
</form>
);
}

export default Form;
2 changes: 1 addition & 1 deletion src/components/HowItWorks.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const HowItWorks = () => {
<ul>
<li>A Quick way to invoice your clients</li>
<li>Late invoice reminders</li>
<li>Accountign reports that make tax-time infinitely more bearable</li>
<li>Accounting reports that make tax-time infinitely more bearable</li>
</ul>
</div>
);
Expand Down
13 changes: 0 additions & 13 deletions src/components/SubmitMessage.js

This file was deleted.

11 changes: 11 additions & 0 deletions src/components/SubmitMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";

function SubmitMessage ({status, message}:{status: string; message: string}){
return (
<p className={`submit-message submit-message--${status}`}>
{message}
</p>
);
}

export default SubmitMessage;
Loading