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
24 changes: 17 additions & 7 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
# 99Tech Code Challenge #1 #
# James Nguyen Submission
Following the code challenge for the **Frontend Developer** position, I completed **Problems 1, 2, and 3** located in the `src` directory.

Note that if you fork this repository, your responses may be publicly linked to this repo.
Please submit your application along with the solutions attached or linked.
### Problem 1 **[View problem solved](src/problem1/index.ts)**

It is important that you minimally attempt the problems, even if you do not arrive at a working solution.
### Problem 2 **[View Problem Documentation](src/problem2/README.md)**

### Problem 3 **[View problem comment](src/problem3/messy-file.tsx)**
This was my favorite part of the challenge, as it closely reflects **real-world development scenarios**. I jump on it right after finishing the first problem.
The problem required careful code reading, reasoning about existing behavior, and working through imperfect or messy implementations—something developers face daily.

Overall, I found this problem to be the most realistic and intellectually engaging part of the challenge.

## Final Note AI Assistance Disclosure

With the increasing adoption of AI tools in software development and based on the instruction, I would like to be transparent about how AI assisted me during this challenge. How I simply used AI in this project was that I first coded the initial part by myself, then used AI to help complete the line. However, if the generated code did not fit my idea or logic, I would not accept it and would adjust or rewrite it.

I view AI as a productivity tool rather than a replacement for engineering judgment.
Throughout this challenge, my focus was on understanding the problem deeply, making technical decisions on my own, and using AI selectively to accelerate — not replace — my thinking process.

## Submission ##
You can either provide a link to an online repository, attach the solution in your application, or whichever method you prefer.
We're cool as long as we can view your solution without any pain.
Empty file removed src/problem1/.keep
Empty file.
33 changes: 33 additions & 0 deletions src/problem1/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* first idea in my head was using reduce function, i know the performance it not too good with many loop to generate and calculate,
* but the logic is clear by using the previous value to accumulate the sum. better way can a for loop but i like reduce more.
*/
const sum_to_n_a = function (n: number): number {
return Array.from({ length: n }, (_, i) => i + 1).reduce(
(acc, val) => acc + val,
0
);
};

/*
* my second idea was using math, because i love math so i found that if we calculate the sum of an arithmetic sequence,
* we have the last number of the list plus the first number is equal the n-1 number plus the second number, and so on. so we have n/2 pairs of (n+1).
* but then if n is odd number the middle number will be alone without any pair. so we have n/2 -1 pares of (n+1) plus the middle number
* but the special thing is double the middled number is equal to n + 1. then we can convert it back to n/2 pairs of (n+1).
* so finally we have n/2 * (n + 1).
*/
const sum_to_n_b = function (n: number): number {
return n / 2 * (n + 1);
};

/*
* actually i have little block with the 3rd idea, originally i want to use while loop to accumulate the sum, but then i think it may not good enough.
* then i try to do a little research and found there are many answers in the internet lol. and i know that recursion can solve the problem.
* then i implement it as the third way to deal with it, hope its still acceptable for the test.
*/
const sum_to_n_c = function (n: number): number {
if (n <= 1) {
return n;
}
return n + sum_to_n_c(n - 1);
};
24 changes: 24 additions & 0 deletions src/problem2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
167 changes: 167 additions & 0 deletions src/problem2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Problem 2 - Fancy Form assignment #

## Project Overview
Create a **currency swap form** based on the template provided in the project folder.
This form allows users to swap assets from one currency to another.

## Implementation Idea

The application is designed as a **simple wallet management app** for a single user.

### Initial State
- The user starts with **1,000,000 USD** in their USD wallet.
- Each currency is represented as a separate wallet.

### Pricing & Token Data
- Token prices are fetched from the following API:
https://interview.switcheo.com/prices.json
- Conversion rates are applied **in real time**:
- When the user selects/search the target currency
- When the user enters the amount to swap
- Conversion also works in reverse (changing the target amount recalculates the source amount)

### Validation Rules
The form validates the following cases:
- Empty input fields
- Negative or zero amounts
- Insufficient balance in the source wallet

### API Simulation
- A small artificial delay is added to simulate a real API call during the swap process


## folder structures
```
problem2/
├── public/ # Static assets
└── src/
├── main.tsx # Contain provider and render app
├── App.tsx # Simple App component
├── theme.ts # Configure Material-UI theme
├── api/ # API layer contain data fetching logic hooks and services
├── atom/ # Reusable UI components (Atomic Design)
├── components/ # Feature components containing business UI and logic
├── contexts/ # React contexts for modal and notification
├── types/ # TypeScript types & schemas
└── common/ # Utilities & constants
```

## Tech Stack

### Core Framework
- **React 19.2.0** with **TypeScript ~5.9.3**
- Uses React built-in hooks and Context API for state management
- Type-safe development with strict TypeScript configuration

### Build Tool & Development
- **Vite (Rolldown-Vite 7.2.5)** - Next-gen build tool with HMR
- Path alias mapping (`@components`, `@api`, `@types`, etc.)

### UI Framework
- **Material-UI v7.3.7** (@mui/material)
- Component library with Material Design
- Responsive Grid system

### State Management & Data Fetching
- **TanStack React Query v5.90.18**
- Server state management
- Automatic caching, refetching and background updates

### Form Handling & Validation
- **Zod v4.3.5** - TypeScript-first schema validation
- Runtime type checking

### HTTP Client
- **Axios v1.13.2**
- Promise-based HTTP client
- Request/response interceptors

---

## Architecture & Best Practices

### 1. **Layered Architecture**
```
Presentation Layer (Components)
Business Logic Layer (Hooks)
Data Access Layer (Services)
API Client Layer (Axios)
```

#### **Separation of Concerns**
- **Components**: Handle UI rendering and user interaction only
- **Custom Hooks**: Business logic, state management, side effects
- **Services**: API calls, data transformation
- **Contexts**: Cross-cutting concerns (modals, notifications)

### 2. **Atomic Design Pattern**
```
src/atom/ # Atomic components (Button, Input, Autocomplete)
src/components/ # Feature components (WalletCard, ExchangeForm)
```

- Reusable UI components in `/atom`
- Composition-based component design
- Props typing with generics for reusability

**Example**: `CommonAutocomplete<T>` - Generic component that works with any data type

### 3. **Custom Hooks Pattern**

#### **Data Fetching Hooks** (`useWallets`, `useAllPrices`)
```typescript
// Query keys pattern for cache management
export const walletQueryKeys = {
all: ["wallets"] as const,
};

// Shared query config
export const queryConfig = {
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 30 * 60 * 1000, // 30 minutes
retry: 2,
};
```

#### **Business Logic Hooks** (`useExchangeForm`)
- Form state management
- Field-level validation
- Real-time conversion calculation
- Bidirectional data sync (from ↔ to amounts)

### 4. **Performance Optimizations**

#### **Memoization**
```typescript
// useMemo for expensive calculations
const pricesMap = useMemo(() => getLatestPricesMap(prices), [prices]);

// useCallback for event handlers
const handleAmountChange = useCallback((field, value) => {
// ...
}, [dependencies]);
```

#### **React Query Caching**
- 5 minutes stale time → Reduce unnecessary refetches
- 30 minutes garbage collection → Balance memory usage
- Background refetching on window focus

#### **Component Optimization**
- Skeleton loading states
- Conditional rendering with early returns
- Grid virtualization with MUI Grid v2

## Key Takeaways

✅ **Type Safety First**: TypeScript strict mode + Zod validation
✅ **Separation of Concerns**: Layered architecture with clear boundaries
✅ **Performance**: React Query caching + memoization
✅ **Developer Experience**: Path aliases + hot reload + ESLint
✅ **User Experience**: Loading states + error handling + real-time validation
✅ **Code Quality**: Consistent patterns + reusable components + documentation
✅ **Scalability**: Modular structure + easy to extend

23 changes: 23 additions & 0 deletions src/problem2/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])
38 changes: 12 additions & 26 deletions src/problem2/index.html
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
<html>

<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fancy Form</title>

<!-- You may add more stuff here -->
<link href="style.css" rel="stylesheet" />
</head>

<body>

<!-- You may reorganise the whole HTML, as long as your form achieves the same effect. -->
<form onsubmit="return !1">
<h5>Swap</h5>
<label for="input-amount">Amount to send</label>
<input id="input-amount" />

<label for="output-amount">Amount to receive</label>
<input id="output-amount" />

<button>CONFIRM SWAP</button>
</form>
<script src="script.js"></script>
</body>

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vite-project</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading