diff --git a/src/App.tsx b/src/App.tsx index 9b26d14..c993aa7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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 = { @@ -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); @@ -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); diff --git a/src/components/InvoiceForm.tsx b/src/components/InvoiceForm.tsx index 00871ac..afd2dd1 100644 --- a/src/components/InvoiceForm.tsx +++ b/src/components/InvoiceForm.tsx @@ -20,11 +20,14 @@ export const InvoiceForm = ({ invoiceData, onGenerate, onDownload, pdfBlob, onUp const [isItemsExpanded, setIsItemsExpanded] = useState(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'); @@ -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(() => { @@ -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 || '', @@ -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 || '', @@ -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 || '', @@ -284,6 +318,33 @@ export const InvoiceForm = ({ invoiceData, onGenerate, onDownload, pdfBlob, onUp /> + ) : isUSDCurrency(invoiceData.currency) ? ( + + + IBAN + onUpdateField?.('iban', e.target.value)} + /> + + + Swift/BIC + onUpdateField?.('swiftBic', e.target.value)} + /> + + + Account Name + onUpdateField?.('accountName', e.target.value)} + /> + + ) : ( diff --git a/src/components/InvoicePDF.tsx b/src/components/InvoicePDF.tsx index dbb888a..ec45f49 100644 --- a/src/components/InvoicePDF.tsx +++ b/src/components/InvoicePDF.tsx @@ -254,6 +254,19 @@ export const InvoicePDF = ({ data }: InvoicePDFProps) => { {data.iban || ''} ); + case 'USD': + return ( + <> + + IBAN: + {data.iban || ''} + + + Swift/BIC: + {data.swiftBic || ''} + + + ); case 'GBP': default: return ( diff --git a/src/types/invoice.ts b/src/types/invoice.ts index e7126fe..3ae80b4 100644 --- a/src/types/invoice.ts +++ b/src/types/invoice.ts @@ -14,6 +14,7 @@ export interface InvoiceData { accountNumber: string; accountName: string; iban: string; + swiftBic: string; currency: string; items: InvoiceItem[]; } @@ -30,4 +31,5 @@ export interface BankDetailsUpdate { accountNumber?: string; accountName: string; iban?: string; + swiftBic?: string; } diff --git a/src/utils/bankDetails.ts b/src/utils/bankDetails.ts index 82666c2..8ddec0f 100644 --- a/src/utils/bankDetails.ts +++ b/src/utils/bankDetails.ts @@ -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[] = [ @@ -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)]; diff --git a/src/utils/currencies.ts b/src/utils/currencies.ts index 3275797..23ddd8e 100644 --- a/src/utils/currencies.ts +++ b/src/utils/currencies.ts @@ -17,6 +17,12 @@ export const currencies: Currency[] = [ name: 'Euro', symbol: '€', flag: 'πŸ‡ͺπŸ‡Ί' + }, + { + code: 'USD', + name: 'US Dollar', + symbol: '$', + flag: 'πŸ‡ΊπŸ‡Έ' } ]; diff --git a/src/utils/invoiceGenerator.ts b/src/utils/invoiceGenerator.ts index 75f1d95..56bee18 100644 --- a/src/utils/invoiceGenerator.ts +++ b/src/utils/invoiceGenerator.ts @@ -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 }); @@ -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 })}`, @@ -32,7 +33,8 @@ export const generateFakeInvoice = (): InvoiceData => { accountNumber: defaultBank.accountNumber || '', accountName: defaultBank.name, iban: defaultBank.iban || '', - currency: 'GBP', + swiftBic: defaultBank.swiftBic || '', + currency, items, }; };