From 11a3e9832ce3f7c9a1518166155dfb20cf377ff9 Mon Sep 17 00:00:00 2001 From: loitd296 Date: Sat, 28 Feb 2026 21:28:37 +0700 Subject: [PATCH 01/11] feat: 99Tech code challenge - Problem 1, 2, 3 completed --- .gitignore | 39 + COMMIT_AND_PUSH.md | 88 + readme.md | 107 +- src/problem1/readme.md | 39 + src/problem1/sum.js | 24 + src/problem2/.gitignore | 24 + src/problem2/README.md | 71 + src/problem2/eslint.config.js | 23 + src/problem2/index.html | 42 +- src/problem2/package-lock.json | 3032 +++++++++++++++++++ src/problem2/package.json | 33 + src/problem2/public/vite.svg | 1 + src/problem2/script.js | 0 src/problem2/src/App.css | 299 ++ src/problem2/src/App.tsx | 12 + src/problem2/src/api/prices.ts | 33 + src/problem2/src/components/SwapForm.tsx | 263 ++ src/problem2/src/components/TokenIcon.tsx | 65 + src/problem2/src/components/TokenSelect.tsx | 34 + src/problem2/src/index.css | 34 + src/problem2/src/main.tsx | 10 + src/problem2/src/types.ts | 14 + src/problem2/style.css | 8 - src/problem2/tsconfig.app.json | 28 + src/problem2/tsconfig.json | 7 + src/problem2/tsconfig.node.json | 26 + src/problem2/vite.config.ts | 7 + src/problem3/ANALYSIS.md | 350 +++ src/problem3/WalletPage.refactored.tsx | 124 + src/problem3/readme.md | 44 + 30 files changed, 4840 insertions(+), 41 deletions(-) create mode 100644 .gitignore create mode 100644 COMMIT_AND_PUSH.md create mode 100644 src/problem1/readme.md create mode 100644 src/problem1/sum.js create mode 100644 src/problem2/.gitignore create mode 100644 src/problem2/README.md create mode 100644 src/problem2/eslint.config.js create mode 100644 src/problem2/package-lock.json create mode 100644 src/problem2/package.json create mode 100644 src/problem2/public/vite.svg delete mode 100644 src/problem2/script.js create mode 100644 src/problem2/src/App.css create mode 100644 src/problem2/src/App.tsx create mode 100644 src/problem2/src/api/prices.ts create mode 100644 src/problem2/src/components/SwapForm.tsx create mode 100644 src/problem2/src/components/TokenIcon.tsx create mode 100644 src/problem2/src/components/TokenSelect.tsx create mode 100644 src/problem2/src/index.css create mode 100644 src/problem2/src/main.tsx create mode 100644 src/problem2/src/types.ts delete mode 100644 src/problem2/style.css create mode 100644 src/problem2/tsconfig.app.json create mode 100644 src/problem2/tsconfig.json create mode 100644 src/problem2/tsconfig.node.json create mode 100644 src/problem2/vite.config.ts create mode 100644 src/problem3/ANALYSIS.md create mode 100644 src/problem3/WalletPage.refactored.tsx create mode 100644 src/problem3/readme.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..00d8845d40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Dependencies — không push +node_modules/ + +# Build output — không push +dist/ +dist-ssr/ +*.tsbuildinfo + +# Env / secrets — không push +.env +.env.* +!.env.example +*.local + +# Logs & debug +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +logs/ + +# OS / Editor +.DS_Store +Thumbs.db +.idea/ +.vscode/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*~ + +# Optional: bỏ comment dòng dưới nếu không muốn push lock file +# package-lock.json +# pnpm-lock.yaml +# yarn.lock diff --git a/COMMIT_AND_PUSH.md b/COMMIT_AND_PUSH.md new file mode 100644 index 0000000000..82691f0bd8 --- /dev/null +++ b/COMMIT_AND_PUSH.md @@ -0,0 +1,88 @@ +# Commit & push bằng Git Bash (cho nhà tuyển dụng) + +Mở **Git Bash**, cd vào thư mục repo (có file `.git`): + +```bash +cd /c/Users/loi.tran/Documents/Projects/99tech/code-challenge +``` + +--- + +## Files không được push (đã nằm trong `.gitignore`) + +Các file/folder sau **sẽ không** bị đẩy lên khi bạn `git add .` và `git push`: + +| Bỏ qua | Mục đích | +|--------|----------| +| `node_modules/` | Dependencies (nhà tuyển dụng chạy `npm install`) | +| `dist/`, `dist-ssr/` | Build output (tạo bằng `npm run build`) | +| `.env`, `.env.*`, `*.local` | Biến môi trường / secret | +| `*.log`, `logs/` | Log, debug | +| `.idea/`, `.vscode/`, `.DS_Store` | OS / Editor | +| `*.tsbuildinfo` | Cache TypeScript | + +**Giữ lại (có push):** `package.json`, `package-lock.json`, source code, README — nhà tuyển dụng clone và chạy được ngay. + +--- + +## 1. Kiểm tra + +```bash +git status +git remote -v +``` + +Branch hiện tại: **solution/frontend**. Nếu chưa có remote: + +```bash +git remote add origin https://github.com//.git +``` + +--- + +## 2. Stage & commit + +```bash +git add . +git status +``` + +Xác nhận trong list **không** có `node_modules` hay `dist`. Rồi commit: + +```bash +git commit -m "feat: 99Tech code challenge - Problem 1, 2, 3 completed" +``` + +Nếu gặp lỗi `error: option 'trailer' requires a value`: có thể do alias Git của bạn. Thử: + +```bash +git commit --no-verify -m "Complete 99Tech code challenge Problem 1 2 3" +``` + +--- + +## 3. Push lên fork + +```bash +git push -u origin solution/frontend +``` + +Nếu fork dùng branch `main` và bạn muốn push lên đó: + +```bash +git push -u origin solution/frontend:main +``` + +Hoặc đổi branch local sang `main` rồi push: + +```bash +git checkout -b main +git push -u origin main +``` + +--- + +## 4. Sau khi push + +- Mở fork trên GitHub và kiểm tra code + README. +- Gửi link repo (hoặc link PR) cho nhà tuyển dụng. diff --git a/readme.md b/readme.md index 1ff4bc95b4..a6a96975fd 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,103 @@ -# 99Tech Code Challenge #1 # +# 99Tech Code Challenge -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. +> Solutions for the 99Tech software engineering code challenge. -It is important that you minimally attempt the problems, even if you do not arrive at a working solution. +## Table of contents -## 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. +| Problem | Title | Description | Key tech | +|---------|-------|-------------|----------| +| [Problem 1](src/problem1/) | Three Ways to Sum | 3 implementations of `sum_to_n` with different algorithmic approaches | JavaScript | +| [Problem 2](src/problem2/) | Fancy Form (Currency Swap) | Interactive currency swap form with live conversion rates | React 19, TypeScript, Vite 8 | +| [Problem 3](src/problem3/) | Messy React (Code Review) | 13 issues identified + refactored version of a buggy React component | TypeScript, React analysis | + +--- + +## Problem 1 — Three Ways to Sum + +Three implementations of `sum_to_n(n) = 1 + 2 + … + n`: + +| Variant | Approach | Time | Space | +|---------|----------|------|-------| +| A | Iterative loop | O(n) | O(1) | +| B | Gauss formula | O(1) | O(1) | +| C | Recursion | O(n) | O(n) | + +Each includes input validation for non-integer/negative values. + +**[→ Full README](src/problem1/readme.md)** + +--- + +## Problem 2 — Fancy Form (Currency Swap) + +A modern, responsive swap form built from scratch with **React 19 + TypeScript + Vite 8**. + +**Highlights:** +- Live token prices from Switcheo API with deduplication +- Real-time conversion + exchange rate display +- Token icons from Switcheo repo with graceful fallback +- Input validation with accessible error messages +- Simulated swap submission with loading spinner +- Dark/Light mode following system preference +- Responsive design (320 px+) +- No external UI library — pure CSS + +**Run it:** +```bash +cd src/problem2 +npm install +npm run dev +``` + +**[→ Full README](src/problem2/README.md)** + +--- + +## Problem 3 — Messy React (Code Review) + +Identified **13 issues** in the provided React component across 5 categories: + +| Category | Count | Examples | +|----------|-------|---------| +| Bugs | 4 | Undefined variable in filter, inverted logic, missing sort return, dead `formattedBalances` | +| Type errors | 2 | Missing `blockchain` on interface, `any` parameter | +| Performance | 2 | Unnecessary `useMemo` dep, function recreated per render | +| Anti-patterns | 1 | Array index as React key | +| Robustness | 1 | Unsafe `undefined * number` → NaN | +| Style/Logic | 3 | Empty interface, unused destructuring, missing `classes` | + +Includes a fully refactored `WalletPage.refactored.tsx` with all fixes applied. + +**[→ Full README](src/problem3/readme.md)** · **[→ Detailed Analysis](src/problem3/ANALYSIS.md)** + +--- + +## Repository structure + +``` +code-challenge/ +├── readme.md ← You are here +└── src/ + ├── problem1/ + │ ├── sum.js ← 3 implementations + │ └── readme.md + ├── problem2/ + │ ├── index.html + │ ├── package.json + │ ├── vite.config.ts + │ ├── README.md + │ └── src/ + │ ├── main.tsx + │ ├── App.tsx / App.css + │ ├── index.css + │ ├── types.ts + │ ├── api/prices.ts + │ └── components/ + │ ├── SwapForm.tsx + │ ├── TokenIcon.tsx + │ └── TokenSelect.tsx + └── problem3/ + ├── readme.md + ├── ANALYSIS.md ← Detailed issue breakdown + └── WalletPage.refactored.tsx ← Clean version +``` diff --git a/src/problem1/readme.md b/src/problem1/readme.md new file mode 100644 index 0000000000..7426893dbf --- /dev/null +++ b/src/problem1/readme.md @@ -0,0 +1,39 @@ +# Problem 1 — Three Ways to Sum to N + +## Task + +Provide three unique implementations of `sum_to_n` that each return `1 + 2 + … + n`. + +**Input:** `n` — any integer +**Output:** summation to `n`, i.e. `sum_to_n(5) === 1 + 2 + 3 + 4 + 5 === 15` + +When `n ≤ 0`, the result is `0`. + +## Implementations + +| Variant | Approach | Time Complexity | Space Complexity | +|---------|----------|-----------------|------------------| +| **A** | Iterative loop | O(n) | O(1) | +| **B** | Gauss formula `n*(n+1)/2` | O(1) | O(1) | +| **C** | Recursion | O(n) | O(n) — call stack | + +### A — Iterative + +Simple `for` loop accumulating the sum. Straightforward, no risk of stack overflow. + +### B — Mathematical (Gauss) + +Uses the closed-form formula. Constant time — the most efficient approach. Works correctly for all integer values within JavaScript's safe integer range. + +### C — Recursive + +Each call adds `n` and recurses with `n - 1`. Elegant but limited by the call stack depth (~10 000 in most JS engines). Included to demonstrate a different algorithmic pattern. + +## Examples + +``` +sum_to_n(5) → 15 +sum_to_n(1) → 1 +sum_to_n(0) → 0 +sum_to_n(-3) → 0 +``` diff --git a/src/problem1/sum.js b/src/problem1/sum.js new file mode 100644 index 0000000000..9188005c90 --- /dev/null +++ b/src/problem1/sum.js @@ -0,0 +1,24 @@ +/** + * Three implementations of sum_to_n. + * Input: n — any integer. + * Output: 1 + 2 + … + n (returns 0 when n ≤ 0). + */ + +// A — Iterative: O(n) time, O(1) space +var sum_to_n_a = function (n) { + let sum = 0; + for (let i = 1; i <= n; i++) sum += i; + return sum; +}; + +// B — Mathematical (Gauss formula): O(1) time, O(1) space +var sum_to_n_b = function (n) { + if (n <= 0) return 0; + return (n * (n + 1)) / 2; +}; + +// C — Recursive: O(n) time, O(n) space (call stack) +var sum_to_n_c = function (n) { + if (n <= 0) return 0; + return n + sum_to_n_c(n - 1); +}; diff --git a/src/problem2/.gitignore b/src/problem2/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/src/problem2/.gitignore @@ -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? diff --git a/src/problem2/README.md b/src/problem2/README.md new file mode 100644 index 0000000000..d1a808151c --- /dev/null +++ b/src/problem2/README.md @@ -0,0 +1,71 @@ +# Problem 2 — Fancy Form (Currency Swap) + +A modern, responsive currency swap form built with **React 19 + TypeScript + Vite**. + +## Live features + +| Feature | Detail | +|---------|--------| +| **Token list** | Fetched from `interview.switcheo.com/prices.json`; deduplicated (latest entry per currency) | +| **Token icons** | Loaded from [Switcheo token-icons](https://github.com/Switcheo/token-icons) with letter fallback on 404 | +| **Real-time conversion** | Amount × (fromPrice / toPrice) updates instantly as you type | +| **Exchange rate display** | Shows `1 FROM = X.XX TO` below the form | +| **Input validation** | Invalid number, zero/negative amount, same-token swap | +| **Loading & submit simulation** | Spinner on initial load; 1.5 s simulated swap with success message | +| **Swap direction** | Circular button swaps From ↔ To and auto-fills the converted amount | +| **Accessibility** | `aria-invalid`, `aria-describedby`, `aria-busy`, `role="alert"`, `