diff --git a/src/problem1/index.js b/src/problem1/index.js
new file mode 100644
index 000000000..3d361ef4b
--- /dev/null
+++ b/src/problem1/index.js
@@ -0,0 +1,44 @@
+var sum_to_n_a = function (n) {
+ // your code here
+
+ // Recursive function approach
+ if (n < 0) return 0;
+
+ if ([0, 1].includes(n)) return n;
+
+ // Complexity: O(n)
+ return n + sum_to_n_a(n - 1);
+};
+
+var sum_to_n_b = function (n) {
+ // your code here
+
+ // Iterative function approach
+ if (n < 0) return 0;
+ if ([0, 1].includes(n)) return n;
+
+ // Complexity: O(n)
+ let sum = 0;
+ for (let i = 0; i <= n; i++) {
+ sum += i;
+ }
+ return sum;
+};
+
+var sum_to_n_c = function (n) {
+ // your code here
+
+ // Mathematical formula approach
+ if (n < 0) return 0;
+ if ([0, 1].includes(n)) return n;
+
+ /**
+ * S = 1 + 2 + 3 + ... + n
+ * S = n + (n-1) + (n-2) + ... + 1
+ * ─────────────────────────────────
+ * 2S = (n+1) + (n+1) + (n+1) + ... + (n+1)
+ * S = (n * (n + 1)) / 2
+ */
+ // Complexity: O(1)
+ return (n * (n + 1)) / 2;
+};
diff --git a/src/problem2/index.html b/src/problem2/index.html
index 4058a68bf..600ba90e1 100644
--- a/src/problem2/index.html
+++ b/src/problem2/index.html
@@ -1,27 +1,97 @@
-
+
+
-
+
+
Fancy Form
-
+
+
+
+
+
-
-
-
+
+
-
+
\ No newline at end of file
diff --git a/src/problem2/script.js b/src/problem2/script.js
index e69de29bb..d0310007a 100644
--- a/src/problem2/script.js
+++ b/src/problem2/script.js
@@ -0,0 +1,301 @@
+const TOKEN_SVG_API = 'https://raw.githubusercontent.com/Switcheo/token-icons/main/tokens';
+const CURRENCY_API = 'https://interview.switcheo.com/prices.json'
+
+let tokenIconsCache = {};
+let currencyData = [];
+
+async function getTokenData(token) {
+ try {
+ // Check cache first
+ if (tokenIconsCache[token]) {
+ return tokenIconsCache[token];
+ }
+
+ const response = await fetch(`${TOKEN_SVG_API}/${token}.svg`);
+ if (response.ok) {
+ const svgText = await response.text();
+ tokenIconsCache[token] = svgText;
+ return svgText;
+ }
+ return null;
+ } catch (error) {
+ console.error(`Error fetching icon for ${token}:`, error);
+ return null;
+ }
+}
+
+async function updateCurrencyIcon(currency, iconElementId) {
+ const iconElement = document.getElementById(iconElementId);
+ if (!currency || !iconElement) {
+ if (iconElement) {
+ iconElement.innerHTML = '';
+ }
+ return;
+ }
+
+ const iconSvg = await getTokenData(currency);
+ if (iconSvg) {
+ iconElement.innerHTML = iconSvg;
+ // Style the SVG inside
+ const svg = iconElement.querySelector('svg');
+ if (svg) {
+ svg.style.width = '24px';
+ svg.style.height = '24px';
+ }
+ } else {
+ // Fallback to currency initials if icon not available
+ iconElement.innerHTML = currency.substring(0, 2).toUpperCase();
+ iconElement.style.fontSize = '10px';
+ iconElement.style.fontWeight = 'bold';
+ }
+}
+
+async function getCurrencyData() {
+ const response = await fetch(CURRENCY_API);
+ return response.json();
+}
+
+function calculateSwapAmount() {
+ const inputCurrency = document.getElementById('input-currency').value;
+ const outputCurrency = document.getElementById('output-currency').value;
+ const inputAmount = parseFloat(document.getElementById('input-amount').value) || 0;
+ const outputAmountField = document.getElementById('output-amount');
+
+ if (!inputCurrency || !outputCurrency) {
+ outputAmountField.value = '';
+ return;
+ }
+
+ // Find currency prices
+ const inputCurrencyData = currencyData.find(c => c.currency === inputCurrency);
+ const outputCurrencyData = currencyData.find(c => c.currency === outputCurrency);
+
+ if (!inputCurrencyData || !outputCurrencyData) {
+ outputAmountField.value = '';
+ return;
+ }
+
+ if (inputAmount > 0) {
+ const inputPrice = parseFloat(inputCurrencyData.price);
+ const outputPrice = parseFloat(outputCurrencyData.price);
+
+ // Calculate: (inputAmount * inputPrice) / outputPrice
+ const outputAmount = (inputAmount * inputPrice) / outputPrice;
+ outputAmountField.value = outputAmount.toFixed(6);
+ } else {
+ outputAmountField.value = '';
+ }
+}
+
+function showError(fieldId, errorMessage) {
+ const field = document.getElementById(fieldId);
+ const errorElement = document.getElementById(`${fieldId}-error`);
+
+ if (field && errorElement) {
+ field.classList.add('is-invalid');
+ field.classList.remove('is-valid');
+ errorElement.textContent = errorMessage;
+ errorElement.classList.add('show');
+ }
+}
+
+function clearError(fieldId) {
+ const field = document.getElementById(fieldId);
+ const errorElement = document.getElementById(`${fieldId}-error`);
+
+ if (field && errorElement) {
+ field.classList.remove('is-invalid');
+ field.classList.add('is-valid');
+ errorElement.textContent = '';
+ errorElement.classList.remove('show');
+ }
+}
+
+function validateInputCurrency() {
+ const inputCurrency = document.getElementById('input-currency').value;
+ const outputCurrency = document.getElementById('output-currency').value;
+
+ if (!inputCurrency) {
+ showError('input-currency', 'Please select a currency to send from.');
+ return false;
+ }
+
+ if (inputCurrency && outputCurrency && inputCurrency === outputCurrency) {
+ showError('input-currency', 'Please select a different currency from the "To" currency.');
+ return false;
+ }
+
+ clearError('input-currency');
+ return true;
+}
+
+function validateOutputCurrency() {
+ const inputCurrency = document.getElementById('input-currency').value;
+ const outputCurrency = document.getElementById('output-currency').value;
+
+ if (!outputCurrency) {
+ showError('output-currency', 'Please select a currency to receive.');
+ return false;
+ }
+
+ if (inputCurrency && outputCurrency && inputCurrency === outputCurrency) {
+ showError('output-currency', 'Please select a different currency from the "From" currency.');
+ return false;
+ }
+
+ clearError('output-currency');
+ return true;
+}
+
+function validateInputAmount() {
+ const inputAmount = parseFloat(document.getElementById('input-amount').value) || 0;
+
+ if (inputAmount <= 0) {
+ showError('input-amount', 'Please enter a valid amount greater than 0.');
+ return false;
+ }
+
+ clearError('input-amount');
+ return true;
+}
+
+function validateForm() {
+ const inputCurrencyValid = validateInputCurrency();
+ const outputCurrencyValid = validateOutputCurrency();
+ const inputAmountValid = validateInputAmount();
+
+ return inputCurrencyValid && outputCurrencyValid && inputAmountValid;
+}
+
+function handleFormSubmit(event) {
+ event.preventDefault();
+
+ if (!validateForm()) {
+ return;
+ }
+
+ const inputCurrency = document.getElementById('input-currency').value;
+ const outputCurrency = document.getElementById('output-currency').value;
+ const inputAmount = parseFloat(document.getElementById('input-amount').value);
+ const outputAmount = parseFloat(document.getElementById('output-amount').value);
+
+ // Find currency data for display
+ const inputCurrencyData = currencyData.find(c => c.currency === inputCurrency);
+ const outputCurrencyData = currencyData.find(c => c.currency === outputCurrency);
+
+ const swapDetails = {
+ from: {
+ currency: inputCurrency.toUpperCase(),
+ amount: inputAmount,
+ price: parseFloat(inputCurrencyData.price)
+ },
+ to: {
+ currency: outputCurrency.toUpperCase(),
+ amount: outputAmount,
+ price: parseFloat(outputCurrencyData.price)
+ }
+ };
+
+ // Log swap details (in a real app, this would be sent to a server)
+ console.log('Swap confirmed:', swapDetails);
+
+ // Populate modal with swap details
+ document.getElementById('modal-sending-details').textContent =
+ `${swapDetails.from.amount} ${swapDetails.from.currency} ($${swapDetails.from.price})`;
+
+ document.getElementById('modal-receiving-details').textContent =
+ `${swapDetails.to.amount.toFixed(6)} ${swapDetails.to.currency} ($${swapDetails.to.price})`;
+
+ // Show modal
+ const modal = new bootstrap.Modal(document.getElementById('swapConfirmationModal'));
+ modal.show();
+
+ // Optionally reset form or keep values
+ // document.getElementById('swap-form').reset();
+}
+
+async function populateCurrencySelects() {
+ currencyData = await getCurrencyData();
+ const inputSelect = document.getElementById('input-currency');
+ const outputSelect = document.getElementById('output-currency');
+
+ // Clear loading messages
+ inputSelect.innerHTML = '';
+ outputSelect.innerHTML = '';
+
+ // Populate both selects with currencies
+ // const currencies = Object.keys(currencyData).sort();
+
+ console.log(currencyData);
+
+ currencyData.forEach(option => {
+ const { currency, price } = option;
+
+ // Create option for input select
+ const inputOption = document.createElement('option');
+ inputOption.value = currency;
+ inputOption.textContent = `${currency.toUpperCase()} ($${parseFloat(price).toFixed(2)})`;
+ inputSelect.appendChild(inputOption);
+
+ // Create option for output select
+ const outputOption = document.createElement('option');
+ outputOption.value = currency;
+ outputOption.textContent = `${currency.toUpperCase()} ($${parseFloat(price).toFixed(2)})`;
+ outputSelect.appendChild(outputOption);
+ });
+
+ // Set default selections if available
+ if (currencyData.length > 0) {
+ inputSelect.value = currencyData[0].currency;
+ await updateCurrencyIcon(currencyData[0].currency, 'input-currency-icon');
+
+ if (currencyData.length > 1) {
+ outputSelect.value = currencyData[1].currency;
+ await updateCurrencyIcon(currencyData[1].currency, 'output-currency-icon');
+ } else {
+ outputSelect.value = currencyData[0].currency;
+ await updateCurrencyIcon(currencyData[0].currency, 'output-currency-icon');
+ }
+ }
+}
+
+async function main() {
+ await populateCurrencySelects();
+
+ // Add event listeners to update icons when selection changes
+ const inputSelect = document.getElementById('input-currency');
+ const outputSelect = document.getElementById('output-currency');
+ const inputAmount = document.getElementById('input-amount');
+ const form = document.getElementById('swap-form');
+
+ inputSelect.addEventListener('change', async (e) => {
+ await updateCurrencyIcon(e.target.value, 'input-currency-icon');
+ calculateSwapAmount();
+ // Validate on change
+ validateInputCurrency();
+ // Also re-validate output currency in case they're now the same
+ validateOutputCurrency();
+ });
+
+ outputSelect.addEventListener('change', async (e) => {
+ await updateCurrencyIcon(e.target.value, 'output-currency-icon');
+ calculateSwapAmount();
+ // Validate on change
+ validateOutputCurrency();
+ // Also re-validate input currency in case they're now the same
+ validateInputCurrency();
+ });
+
+ // Calculate swap amount when input amount changes
+ inputAmount.addEventListener('input', calculateSwapAmount);
+
+ // Inline validation on blur
+ inputSelect.addEventListener('blur', validateInputCurrency);
+ outputSelect.addEventListener('blur', validateOutputCurrency);
+ inputAmount.addEventListener('blur', validateInputAmount);
+
+ // Handle form submission
+ form.addEventListener('submit', handleFormSubmit);
+}
+
+await main();
\ No newline at end of file
diff --git a/src/problem2/style.css b/src/problem2/style.css
index 915af91c7..0fd32c3a5 100644
--- a/src/problem2/style.css
+++ b/src/problem2/style.css
@@ -1,8 +1,111 @@
body {
+ min-height: 100vh;
+ min-width: 360px;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+ background: #f5f5f5;
+ padding: 20px;
+}
+
+.card {
+ border: none;
+ border-radius: 16px;
+ overflow: hidden;
+}
+
+.card-body {
+ background: #ffffff;
+}
+
+.form-control:focus,
+.form-select:focus {
+ border-color: #6c757d;
+ box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.25);
+}
+
+.btn {
+ background-color: #212529;
+ color: #ffffff;
+ border: none;
+ transition: transform 0.2s, box-shadow 0.2s;
+}
+
+.btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+ background-color: #212529;
+ color: #ffffff;
+}
+
+.btn:active {
+ transform: translateY(0);
+}
+
+.input-group-text {
+ background-color: #f8f9fa;
+ border: 1px solid #ced4da;
+ border-right: none;
+ padding: 0.5rem 0.75rem;
display: flex;
- flex-direction: row;
align-items: center;
justify-content: center;
- min-width: 360px;
- font-family: Arial, Helvetica, sans-serif;
+ min-width: 40px;
+ /* Match the height of form-control (default/medium size) */
+ height: calc(1.5em + 0.5rem + 2px);
+}
+
+.form-select:focus+.input-group-text,
+.input-group:focus-within .input-group-text {
+ border-color: #6c757d;
+}
+
+#input-currency-icon,
+#output-currency-icon {
+ width: auto;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+#input-currency-icon svg,
+#output-currency-icon svg {
+ width: auto;
+ height: 100%;
+ object-fit: contain;
+}
+
+/* Validation styles */
+.invalid-feedback {
+ display: none;
+ width: 100%;
+ margin-top: 0.25rem;
+ font-size: 0.875em;
+ color: #dc3545;
+}
+
+.invalid-feedback.show {
+ display: block;
+}
+
+.form-control.is-invalid~.invalid-feedback,
+.input-group+.invalid-feedback.show {
+ display: block;
+}
+
+.is-valid {
+ border-color: #198754;
+}
+
+.is-invalid {
+ border-color: #dc3545;
+}
+
+.is-valid:focus {
+ border-color: #198754;
+ box-shadow: 0 0 0 0.2rem rgba(25, 135, 84, 0.25);
}
+
+.is-invalid:focus {
+ border-color: #dc3545;
+ box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+}
\ No newline at end of file
diff --git a/src/problem3/README.md b/src/problem3/README.md
new file mode 100644
index 000000000..e5ebb3c4f
--- /dev/null
+++ b/src/problem3/README.md
@@ -0,0 +1,111 @@
+# Found Inefficiencies and Anti-patterns
+
+### 1. Inside the `filter()` callback and in the outer scope, there's no declaration of `lhsPriority`
+Suggested fix: replacing `lshPriority` with `balancePriority`
+
+### 2. Inverted filter logic in the in `filter()` callback return.
+
+Suggested fix:
+replace:
+```
+if (lhsPriority > -99) {
+ if (balance.amount <= 0) {
+ return true;
+ }
+}
+```
+
+with
+```
+return lhsPriority > -99 && balance.amount > 0
+```
+
+### 3. Incomplete sort function, missing return for equal priority
+
+- Most recommended solution: sorting the list in the API
+
+- Front-end logic solution:
+
+```
+return rightPriority - leftPriority
+```
+
+### 4. Type `any` inferrence in `getPriority`
+- Suggested fix: replace `balance: any` to `string`
+- Better approach: refactor the `getPriority()`, use index annotation:
+ ```
+ interface BlockchainPriority {
+ [key: string]: number;
+ }
+ const getPriority = (blockchain: string): number => {
+ // In the future may be stored persistently in the database and Redis cache for better performance and scalability, and responds to front-end whenever requested
+ // Trade-off: latency because of API communcation through http
+
+ const blockchainPriority: BlockchainPriority = {
+ Osmosis: 100,
+ Ethereum: 50,
+ Arbitrum: 30
+ Zilliqa: 20,
+ Neo: 20
+ }
+
+ return blockchainPriority[blockchain as keyof BlockchainPriority] || -99;
+ }
+ ```
+
+### 5. `prices` is the redundant dependencies of sortedBalances memo
+
+Inside the computed callback, there's no usage of `prices`, which also added to the `useMemo()` dependencies array => This means even if not using `prices`, the dependency change detected by sortedBalances computing function is unecessary and will also cause unecessary recalculated (while the result remains the same)
+
+Fix: remove the `prices` out of the dependencies array
+
+### 6. `formattedBalances` is recalculrated in every re-render.
+
+The recalculation of `formattedBalances` on every re-render is also unecessary because the results remain the same if the `sortedBalances` stay unchanged
+
+Fix: wrap the computation inside `useMemo()` with `sortedBalances` as the only one dependencies.
+
+### 7. Using index as React key
+
+Using index as React key causes issues in list re-rendering. It is a good practices to use another unique identifier instead. (Such as: `id` fields returns from the API with database querying or `useId()` value)
+
+Fix: in this case specifically, we can use currency as the identifier because the currency name is unique
+
+### 8. Missing `blockchain` field in the `WalletBalance` interface
+
+in the `sortedBalances` computed value, there's an access of `balance.blockchain` which is previously infered with type `WalletBalance.
+
+Suggested fix:
+```
+interface WalletBalance {
+ currency: string;
+ amount: number;
+ blockchain: string; // Add missing property
+}
+```
+
+### 9. Unsafe price computation
+
+In the statement `const usdValue = prices[balance.currency] * balance.amount;`, the logic can't guarantee and predict if `balance.currency` exists in prices because the balance.currency is unpredictable. This can leads to make the calculation to `undefined * balance.amount` which results in `NaN`.
+
+There should be a fallback for accessing `prices[balance.currency]`.
+
+Fix: update it to:
+``` const usdValue = (prices[balance.currency] || 0) * balance.amount;`
+
+### 10. Code repetation in 2 interfaces `WalletBalance` and `FormattedWalletBalance`
+
+Use TypeScript interface extension (inheritance) for better reusability. Fix:
+
+```
+interface WalletBalance {
+ currency: string;
+ amount: number;
+ blockchain: string; // fixed for 8.
+}
+interface FormattedWalletBalance extends WalletBalance {
+ formatted: string;
+}
+```
+
+### [Optional for scalability] 11. API should have supported and returned a sorted list of balances by priority
\ No newline at end of file
diff --git a/src/problem3/Refactor.tsx b/src/problem3/Refactor.tsx
new file mode 100644
index 000000000..8388d04af
--- /dev/null
+++ b/src/problem3/Refactor.tsx
@@ -0,0 +1,62 @@
+interface WalletBalance {
+ currency: string;
+ amount: number;
+ blockchain: string;
+}
+
+interface FormattedWalletBalance extends WalletBalance {
+ formatted: string;
+}
+
+interface BlockchainPriority {
+ [key: string]: number;
+}
+
+const getPriority = (blockchain: string): number => {
+ // TODO: Should have processed in the API response
+ const blockchainPriority: BlockchainPriority = {
+ Osmosis: 100,
+ Ethereum: 50,
+ Arbitrum: 30,
+ Zilliqa: 20,
+ Neo: 20
+ }
+ return blockchainPriority[blockchain as keyof BlockchainPriority] || -99;
+};
+
+const sortedBalances = useMemo(() => {
+ // TODO: should have processed in the API response
+ return balances
+ .filter((balance: WalletBalance) => {
+ const balancePriority = getPriority(balance.blockchain);
+ if (balancePriority > -99 && balance.amount > 0) { // Fixed logic
+ return true;
+ }
+ return false;
+ })
+ .sort((lhs: WalletBalance, rhs: WalletBalance) => {
+ const leftPriority = getPriority(lhs.blockchain);
+ const rightPriority = getPriority(rhs.blockchain);
+ return rightPriority - leftPriority;
+ });
+}, [balances]);
+
+const formattedBalances = useMemo(() => {
+ return sortedBalances.map((balance: WalletBalance) => ({
+ ...balance,
+ formatted: balance.amount.toFixed(2)
+ }));
+}, [sortedBalances]);
+
+const rows = formattedBalances.map((balance: FormattedWalletBalance) => {
+ const usdValue = (prices[balance.currency] || 0) * balance.amount;
+ return (
+
+ );
+});
\ No newline at end of file