Skip to content
Merged
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
7 changes: 5 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function App() {
}, []); // Empty dependency array - only run once on mount

const handleGenerate = async (preserveBankDetails: boolean = false) => {
const newData = generateFakeInvoice();
const newData = generateFakeInvoice(invoiceData.currency as 'GBP' | 'EUR' | 'USD');

// Preserve contact information (client details)
const updatedData = {
Expand All @@ -35,6 +35,8 @@ function App() {
updatedData.sortCode = invoiceData.sortCode;
updatedData.accountNumber = invoiceData.accountNumber;
updatedData.accountName = invoiceData.accountName;
updatedData.iban = invoiceData.iban;
updatedData.swiftBic = invoiceData.swiftBic;
}

setInvoiceData(updatedData);
Expand Down Expand Up @@ -66,7 +68,8 @@ function App() {
accountNumber: bankDetails.accountNumber || '',
accountName: bankDetails.accountName,
companyName: bankDetails.accountName,
iban: bankDetails.iban || ''
iban: bankDetails.iban || '',
swiftBic: bankDetails.swiftBic || ''
};
setInvoiceData(updatedData);

Expand Down
67 changes: 64 additions & 3 deletions src/components/InvoiceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ export const InvoiceForm = ({ invoiceData, onGenerate, onDownload, pdfBlob, onUp
const [isItemsExpanded, setIsItemsExpanded] = useState<boolean>(false);

// Helper function to handle currency-specific logic
const handleCurrencySpecificLogic = (currency: string, callback: (type: 'GBP' | 'EUR') => void) => {
const handleCurrencySpecificLogic = (currency: string, callback: (type: 'GBP' | 'EUR' | 'USD') => void) => {
switch (currency) {
case 'EUR':
callback('EUR');
break;
case 'USD':
callback('USD');
break;
case 'GBP':
default:
callback('GBP');
Expand All @@ -38,13 +41,26 @@ export const InvoiceForm = ({ invoiceData, onGenerate, onDownload, pdfBlob, onUp
case 'EUR':
return true;
case 'GBP':
case 'USD':
default:
return false;
}
};

// Helper function to check if currency is USD
const isUSDCurrency = (currency: string): boolean => {
switch (currency) {
case 'USD':
return true;
case 'GBP':
case 'EUR':
default:
return false;
}
};

// Get bank details based on current currency
const currentBankDetails = getBankDetailsByCurrency(invoiceData.currency as 'GBP' | 'EUR');
const currentBankDetails = getBankDetailsByCurrency(invoiceData.currency as 'GBP' | 'EUR' | 'USD');

// Set initial selected bank details if not set
useEffect(() => {
Expand All @@ -63,6 +79,12 @@ export const InvoiceForm = ({ invoiceData, onGenerate, onDownload, pdfBlob, onUp
accountName: firstBank.name,
iban: firstBank.iban || ''
});
} else if (currencyType === 'USD') {
onUpdateBankDetails?.({
accountName: firstBank.name,
iban: firstBank.iban || '',
swiftBic: firstBank.swiftBic || ''
});
} else {
onUpdateBankDetails?.({
sortCode: firstBank.sortCode || '',
Expand Down Expand Up @@ -129,13 +151,19 @@ export const InvoiceForm = ({ invoiceData, onGenerate, onDownload, pdfBlob, onUp

if (value === 'random') {
// Generate random invalid bank details
const randomDetails = generateRandomInvalidBankDetails(invoiceData.currency as 'GBP' | 'EUR');
const randomDetails = generateRandomInvalidBankDetails(invoiceData.currency as 'GBP' | 'EUR' | 'USD');
handleCurrencySpecificLogic(invoiceData.currency, (currencyType) => {
if (currencyType === 'EUR') {
onUpdateBankDetails?.({
accountName: randomDetails.name,
iban: randomDetails.iban || ''
});
} else if (currencyType === 'USD') {
onUpdateBankDetails?.({
accountName: randomDetails.name,
iban: randomDetails.iban || '',
swiftBic: randomDetails.swiftBic || ''
});
} else {
onUpdateBankDetails?.({
sortCode: randomDetails.sortCode || '',
Expand All @@ -153,6 +181,12 @@ export const InvoiceForm = ({ invoiceData, onGenerate, onDownload, pdfBlob, onUp
accountName: selectedDetails.name,
iban: selectedDetails.iban || ''
});
} else if (currencyType === 'USD') {
onUpdateBankDetails?.({
accountName: selectedDetails.name,
iban: selectedDetails.iban || '',
swiftBic: selectedDetails.swiftBic || ''
});
} else {
onUpdateBankDetails?.({
sortCode: selectedDetails.sortCode || '',
Expand Down Expand Up @@ -284,6 +318,33 @@ export const InvoiceForm = ({ invoiceData, onGenerate, onDownload, pdfBlob, onUp
/>
</Box>
</Grid>
) : isUSDCurrency(invoiceData.currency) ? (
<Grid columns={{ initial: "1", sm: "2", md: "3" }} gap="3">
<Box>
<Text as="label" htmlFor="iban" size="2" weight="bold">IBAN</Text>
<TextField.Root
id="iban"
value={invoiceData.iban}
onChange={(e) => onUpdateField?.('iban', e.target.value)}
/>
</Box>
<Box>
<Text as="label" htmlFor="swiftBic" size="2" weight="bold">Swift/BIC</Text>
<TextField.Root
id="swiftBic"
value={invoiceData.swiftBic}
onChange={(e) => onUpdateField?.('swiftBic', e.target.value)}
/>
</Box>
<Box>
<Text as="label" htmlFor="accountName" size="2" weight="bold">Account Name</Text>
<TextField.Root
id="accountName"
value={invoiceData.accountName}
onChange={(e) => onUpdateField?.('accountName', e.target.value)}
/>
</Box>
</Grid>
) : (
<Grid columns={{ initial: "1", sm: "2", md: "3" }} gap="3">
<Box>
Expand Down
13 changes: 13 additions & 0 deletions src/components/InvoicePDF.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,19 @@ export const InvoicePDF = ({ data }: InvoicePDFProps) => {
<PDFText style={pdfStyles.bankDetailsValue}>{data.iban || ''}</PDFText>
</View>
);
case 'USD':
return (
<>
<View style={pdfStyles.bankDetailsRow}>
<PDFText style={pdfStyles.bankDetailsLabel}>IBAN:</PDFText>
<PDFText style={pdfStyles.bankDetailsValue}>{data.iban || ''}</PDFText>
</View>
<View style={pdfStyles.bankDetailsRow}>
<PDFText style={pdfStyles.bankDetailsLabel}>Swift/BIC:</PDFText>
<PDFText style={pdfStyles.bankDetailsValue}>{data.swiftBic || ''}</PDFText>
</View>
</>
);
case 'GBP':
default:
return (
Expand Down
2 changes: 2 additions & 0 deletions src/types/invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface InvoiceData {
accountNumber: string;
accountName: string;
iban: string;
swiftBic: string;
currency: string;
items: InvoiceItem[];
}
Expand All @@ -30,4 +31,5 @@ export interface BankDetailsUpdate {
accountNumber?: string;
accountName: string;
iban?: string;
swiftBic?: string;
}
63 changes: 60 additions & 3 deletions src/utils/bankDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export interface BankDetails {
sortCode?: string;
accountNumber?: string;
iban?: string;
currency: 'GBP' | 'EUR';
swiftBic?: string;
currency: 'GBP' | 'EUR' | 'USD';
}

export const gbpBankDetails: BankDetails[] = [
Expand Down Expand Up @@ -116,18 +117,74 @@ export const eurBankDetails: BankDetails[] = [
}
];

export const getBankDetailsByCurrency = (currency: 'GBP' | 'EUR'): BankDetails[] => {
export const usdBankDetails: BankDetails[] = [
{
name: "International Committee of the Red Cross",
iban: "CH5200240240C01299864",
swiftBic: "UBSWCHZH80A",
currency: "USD"
},
{
name: "Islamic Relief",
iban: "GB93BARC20077157422611",
swiftBic: "BARCGB22",
currency: "USD"
},
{
name: "ShelterBox",
iban: "GB42NWBK60730157078416",
swiftBic: "NWBKGB2L",
currency: "USD"
},
{
name: "Palestine Red Crescent Society",
iban: "PS18PALS047106073770013000000",
swiftBic: "CITIUS33",
currency: "USD"
}
];

export const getBankDetailsByCurrency = (currency: 'GBP' | 'EUR' | 'USD'): BankDetails[] => {
switch (currency) {
case 'EUR':
return eurBankDetails;
case 'USD':
return usdBankDetails;
case 'GBP':
default:
return gbpBankDetails;
}
};

export const generateRandomInvalidBankDetails = (currency: 'GBP' | 'EUR' = 'GBP'): BankDetails => {
export const generateRandomInvalidBankDetails = (currency: 'GBP' | 'EUR' | 'USD' = 'GBP'): BankDetails => {
switch (currency) {
case 'USD': {
// Generate random IBAN and Swift/BIC for USD
const countryCode = ['US', 'GB', 'CH', 'DE'][Math.floor(Math.random() * 4)];
const checkDigits = Math.floor(Math.random() * 99).toString().padStart(2, '0');
const bankCode = Math.floor(Math.random() * 99999999).toString().padStart(8, '0');
const accountNumber = Math.floor(Math.random() * 9999999999).toString().padStart(10, '0');

const randomIBAN = `${countryCode}${checkDigits} ${bankCode} ${accountNumber}`;

// Generate random Swift/BIC (4 letters bank code + 2 letters country + 2 letters/numbers location)
const bankLetters = String.fromCharCode(65 + Math.floor(Math.random() * 26)) +
String.fromCharCode(65 + Math.floor(Math.random() * 26)) +
String.fromCharCode(65 + Math.floor(Math.random() * 26)) +
String.fromCharCode(65 + Math.floor(Math.random() * 26));
const countryLetters = ['US', 'GB', 'CH', 'DE'][Math.floor(Math.random() * 4)];
const locationCode = String.fromCharCode(65 + Math.floor(Math.random() * 26)) +
(Math.random() > 0.5 ? String.fromCharCode(65 + Math.floor(Math.random() * 26)) : Math.floor(Math.random() * 10));

const randomSwiftBic = bankLetters + countryLetters + locationCode;

return {
name: faker.company.name(),
iban: randomIBAN,
swiftBic: randomSwiftBic,
currency: 'USD'
};
}
case 'EUR': {
// Generate random IBAN for EUR with more realistic format
const countryCode = ['DE', 'FR', 'BE', 'NL', 'AT', 'IT', 'ES'][Math.floor(Math.random() * 7)];
Expand Down
6 changes: 6 additions & 0 deletions src/utils/currencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ export const currencies: Currency[] = [
name: 'Euro',
symbol: '€',
flag: '🇪🇺'
},
{
code: 'USD',
name: 'US Dollar',
symbol: '$',
flag: '🇺🇸'
}
];

Expand Down
10 changes: 6 additions & 4 deletions src/utils/invoiceGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { faker } from '@faker-js/faker';
import type { InvoiceData } from '../types/invoice';
import { gbpBankDetails } from './bankDetails';
import { getBankDetailsByCurrency } from './bankDetails';

export const generateFakeInvoice = (): InvoiceData => {
export const generateFakeInvoice = (currency: 'GBP' | 'EUR' | 'USD' = 'GBP'): InvoiceData => {
const items = Array.from({ length: 2 }, () => {
const quantity = faker.number.int({ min: 1, max: 10 });
const unitPrice = faker.number.float({ min: 10, max: 500, fractionDigits: 2 });
Expand All @@ -14,7 +14,8 @@ export const generateFakeInvoice = (): InvoiceData => {
};
});

const defaultBank = gbpBankDetails[0];
const bankDetails = getBankDetailsByCurrency(currency);
const defaultBank = bankDetails[0];

return {
invoiceNumber: `INV-${faker.number.int({ min: 1000, max: 9999 })}`,
Expand All @@ -32,7 +33,8 @@ export const generateFakeInvoice = (): InvoiceData => {
accountNumber: defaultBank.accountNumber || '',
accountName: defaultBank.name,
iban: defaultBank.iban || '',
currency: 'GBP',
swiftBic: defaultBank.swiftBic || '',
currency,
items,
};
};