This weekend, I finished implementing the loadPaymentMethods()
function to load credit card and bank account information. Below, I'll walk you through the steps I used to solve problems and break down the solution.
Full Implementation
async function loadPaymentMethods(userData) {
if (!userData) return null;
try {
// user profile reference
const userProfileRef = doc(db, "userProfiles", userData.email);
// references to bank and credit card documents
const bankAccountsRef = collection(userProfileRef, "bankAccounts");
const creditCardsRef = collection(userProfileRef, "creditCards");
// fetch bank and credit card information at the same time
const [bankAccountSnapshot, creditCardsSnapshot] = await Promise.all([
getDocs(bankAccountsRef),
getDocs(creditCardsRef)
]);
// map bank information to an object
const bankAccounts = bankAccountSnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data()
}));
// map credit card information to an object
const creditCardAccounts = creditCardsSnapshot.docs.map((doc) => ({
id: doc.id, // credit card id
...doc.data()
}));
// load credit card transactions
const transactionPromises = creditCardAccounts.map(async (card) => {
const cardId = card.id;
// console.log("Card id:", cardId);
const transactions = await loadCreditCardTransactions(userData, cardId);
// console.log("transactions: ", transactions);
return { cardId, transactions };
});
// load bank transactions
const bankTransactionsPromise = bankAccounts.map(async (acc) => {
const bankId = acc.id;
// console.log("bank id: ", bankId);
const transactions = await loadBankTransactions(userData, bankId);
console.log("Bank Account transactions: ", transactions);
return { bankId, transactions };
});
const allBankActivity = await Promise.all(bankTransactionsPromise);
// console.log(allBankActivity);
const allTransactions = await Promise.all(transactionPromises);
// console.log(allTransactions);
const activityElements = allBankActivity.flatMap((bankData) => {
// Return the map array
return bankData.transactions.map((act) => {
const activityItem = document.createElement("div");
activityItem.className = "activity-item";
if (act.type === "Deposit") {
activityItem.innerHTML = `
<div class="activity-item-wrapper">
<label class="details-label font-500">${act.type}</label>
<p class="detail-date">${formatFirebaseDate(act.date)}</p>
</div>
<div class="activity-item-wrapper">
<label class="detail-value font-500 deposit">
+$${act.amount} ${act.currency}
</label>
<p class="status-indicator ${getStatusClass(
act.status
)}" >${act.status}</p>
</div>`;
} else {
activityItem.className = "activity-item";
activityItem.innerHTML = `
<div class="activity-item-wrapper">
<label class="details-label font-500">${act.type}</label>
<p class="detail-date">${formatFirebaseDate(act.date)}</p>
</div>
<div class="activity-item-wrapper">
<label class="detail-value font-500 withdraw">$${
act.amount
} ${act.currency}</label>
<p class="status-indicator ${getStatusClass(act.status)}">${
act.status
}</p>
</div>`;
}
return activityItem; // Return the element
});
});
const recentActivity = document.querySelector("#recent-activity");
recentActivity.innerHTML = "";
// add all elements
activityElements.forEach((element) => {
recentActivity.appendChild(element);
});
// credit cards transactions
const transactionElements = allTransactions.flatMap((cardData) =>
cardData.transactions.map((tran) => {
const transDiv = document.createElement("div");
transDiv.className = "trans";
transDiv.innerHTML = `
<p>${tran.itemName}</p>
<p>$${tran.amount}</p>
`;
return transDiv;
})
);
const recentTransactions = document.querySelector("#r-trans");
// clear existing content
recentTransactions.innerHTML = "";
// add all transaction elements
transactionElements.forEach((element) => {
recentTransactions.appendChild(element);
});
return { bankAccounts, creditCardAccounts };
} catch (error) {
console.error("Error happened when fetching payment information");
throw error; // Propagate error to caller
}
}
Breaking Down the Implementation
- Mapping Bank and Credit Card Information:
Here's how we map the data to objects containing the ID and the best rest of the data from Firebase:
const bankAccounts = bankAccountSnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data()
}));
const creditCardAccounts = creditCardsSnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data()
}));
This helps in better formatting and using the data.
- Loading Transactions
We create promises to load all transactions:
const transactionPromises = creditCardAccounts.map(async (card) => {
const cardId = card.id;
const transactions = await loadCreditCardTransactions(userData, cardId);
return { cardId, transactions };
});
const bankTransactionsPromise = bankAccounts.map(async (acc) => {
const bankId = acc.id;
const transactions = await loadBankTransactions(userData, bankId);
return { bankId, transactions };
});
We use async
before the callback function because these are API calls. We wait for all transactions to be loaded and then map them to their respective accounts.
Handling Promises:
const allBankActivity = await Promise.all(bankTransactionsPromise);
const allTransactions = await Promise.all(transactionPromises);
We use Promise.all()
to wait for all promises to resolve, retrieving transactions from both credit cards and bank accounts.
Creating Activity Elements:
const activityElements = allBankActivity.flatMap((bankData) => {
return bankData.transactions.map((act) => {
const activityItem = document.createElement("div");
activityItem.className = "activity-item";
activityItem.innerHTML = `
<div class="activity-item-wrapper">
<label class="details-label font-500">${act.type}</label>
<p class="detail-date">${formatFirebaseDate(act.date)}</p>
</div>
<div class="activity-item-wrapper">
<label class="detail-value font-500 ${act.type === "Deposit" ? "deposit" : "withdraw"}">
${act.type === "Deposit" ? "+" : ""}$${act.amount} ${act.currency}
</label>
<p class="status-indicator ${getStatusClass(act.status)}">${act.status}</p>
</div>`;
return activityItem;
});
});
We create elements to display recent activity for both bank accounts and credit cards, handling different types of transactions (deposits vs. withdrawals) with appropriate styling.
Stay tuned for the next post where we'll discuss the view detail UI!
Top comments (0)