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
39 changes: 39 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Dependencies (do not commit)
node_modules/

# Build output (do not commit)
dist/
dist-ssr/
*.tsbuildinfo

# Env / secrets (do not commit)
.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: uncomment below to exclude lock files from commit
# package-lock.json
# pnpm-lock.yaml
# yarn.lock
15 changes: 15 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Netlify config for this repo.
# Base directory: app lives in src/problem2 (overrides UI setting).

[build]
base = "src/problem2"
command = "npm run build"
publish = "dist"

[[redirects]]
from = "/*"
to = "/index.html"
status = 200

[build.environment]
NODE_VERSION = "20"
43 changes: 36 additions & 7 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,39 @@
# 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.
A set of three code challenges: algorithms, React/TypeScript form, and code review & refactor.

It is important that you minimally attempt the problems, even if you do not arrive at a working solution.
## Overview

## 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 | Description | Folder |
|---------|-------------|--------|
| **Problem 1** | Three implementations of `sum_to_n` (loop, Gauss formula, recursion) | [src/problem1](./src/problem1) |
| **Problem 2** | Currency swap form (React 19 + TypeScript + Vite + Ant Design), with i18n and Netlify deploy. [Live demo](https://stunning-cascaron-dfac87.netlify.app/) | [src/problem2](./src/problem2) |
| **Problem 3** | Code review & refactor: 13 issues analyzed, clean refactored version | [src/problem3](./src/problem3) |

## Quick start

- **Problem 1**: See [README](./src/problem1/README.md) and [sum.js](./src/problem1/sum.js).
- **Problem 2**:
```bash
cd src/problem2
npm install
npm run dev
```
**Live demo:** [https://stunning-cascaron-dfac87.netlify.app/](https://stunning-cascaron-dfac87.netlify.app/) — Details: [README.md](./src/problem2/README.md).
- **Problem 3**: See [README](./src/problem3/README.md), [ANALYSIS.md](./src/problem3/ANALYSIS.md), and [WalletPage.refactored.tsx](./src/problem3/WalletPage.refactored.tsx).

## Repository structure

```
code-challenge/
├── readme.md # This file — overview & quick start
├── netlify.toml # Netlify deploy config (base: src/problem2)
└── src/
├── problem1/ # Sum to N — 3 implementations
├── problem2/ # Fancy Form — Currency Swap (React + Vite)
└── problem3/ # Messy React — Analysis & refactor
```

---

*See each problem's README for requirements and how to run. For Netlify deploy from repo root, use **Base directory**: `src/problem2` and **Publish directory**: `dist`.*
43 changes: 43 additions & 0 deletions src/problem1/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 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
```

## Solution

Code: [`sum.js`](./sum.js) — exports `sum_to_n_a`, `sum_to_n_b`, `sum_to_n_c`.
24 changes: 24 additions & 0 deletions src/problem1/sum.js
Original file line number Diff line number Diff line change
@@ -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);
};
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?
113 changes: 113 additions & 0 deletions src/problem2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Problem 2 — Fancy Form (Currency Swap)

Currency swap form built with **React 19 + TypeScript + Vite + Ant Design**.

**Live demo:** [https://stunning-cascaron-dfac87.netlify.app/](https://stunning-cascaron-dfac87.netlify.app/)

## Project structure

```
problem2/
├── src/
│ ├── components/ # React components
│ │ ├── SwapForm.tsx
│ │ ├── TokenIcon.tsx
│ │ ├── TokenSelectModal.tsx
│ │ ├── SwapConfirmModal.tsx
│ │ └── SlippageSettings.tsx
│ ├── pages/ # Page components
│ │ └── SwapPage.tsx
│ ├── services/ # API services
│ │ └── prices.ts
│ ├── stores/ # State management (placeholder for MobX/Zustand)
│ │ └── index.ts
│ ├── hooks/ # Custom React hooks
│ │ ├── useTokenPrices.ts
│ │ └── useSwap.ts
│ ├── utils/ # Utility functions
│ │ ├── index.ts # getMockBalance
│ │ └── i18n.ts # t(key, params?) for translations
│ ├── configs/ # Configuration files
│ │ └── index.ts # API URLs, token icon base
│ ├── constants/ # Application constants
│ │ └── index.ts # SLIPPAGE_OPTIONS, POPULAR_TOKENS
│ ├── types/ # TypeScript types
│ │ └── index.ts
│ └── locales/ # Translation files (English keys for maintainability)
│ └── en.json
├── package.json
├── vite.config.ts
├── index.html
└── README.md
```

## Tech stack

- **Vite 5**
- **React 19** + TypeScript
- **Ant Design 5** (antd) — UI components
- **@ant-design/icons**

## Features

- Token list from `interview.switcheo.com/prices.json` (deduplicated by currency)
- Token icons from [Switcheo token-icons](https://github.com/Switcheo/token-icons) with letter fallback
- Searchable token selector modal (Ant Design `Modal` + `Input` + list)
- Slippage settings popover (preset + custom)
- Swap confirmation modal with rate, price impact, min. received
- Real-time conversion, exchange rate, price impact display
- Mock balance + MAX button
- Loading (Spin), error (Alert + retry), success feedback
- Input validation and dynamic submit button text
- **i18n**: All UI strings use `locales/en.json` with **English keys** (e.g. `swap.title`, `swap.youPay`, `swap.amountError.invalidNumber`) so adding new languages later is straightforward; use `t('key')` or `t('key', { param: value })` for template strings.

## Locales (i18n)

- **`src/locales/en.json`**: Keys are in English (e.g. `swap.title`, `swap.errors.fetchPricesFailed`) for easy maintenance and future translation.
- **`src/utils/i18n.ts`**: Exports `t(key, params?)`. Use dot-notation for nested keys; use `{{paramName}}` in the JSON value and pass `{ paramName: value }` for interpolation.
- To add another language: add e.g. `vi.json` with the same key structure and plug a locale selector into the app (e.g. context or hook that chooses which JSON to use in `t()`).

## Getting started

```bash
cd problem2
npm install
npm run dev # → http://localhost:5173
```

### Build

```bash
npm run build
npm run preview
```

### Lint

```bash
npm run lint
```

## Deploy (Netlify)

1. **Push** your code to Git (GitHub, GitLab, or Bitbucket).

2. In **Netlify**: [app.netlify.com](https://app.netlify.com) → **Add new site** → **Import an existing project** → connect your repo.

3. **Build settings** — set these explicitly in the Netlify UI (Site settings → Build & deploy → Build settings → Edit settings):

| Setting | Value |
|--------|--------|
| **Base directory** | `src/problem2` |
| **Build command** | `npm run build` |
| **Publish directory** | `dist` |
| **Functions directory** | *(leave empty or clear)* |
| **Package directory** | *(leave empty)* |

If your repo root is the `problem2` folder itself (single-project repo), leave **Base directory** empty.

4. **Runtime (optional):** Under Build & deploy → Environment → Node version, set **NODE_VERSION** to `20` (or use the variable in `netlify.toml`, which is already set).

5. **Trigger deploy:** Save and run **Trigger deploy** → **Deploy site**. Netlify will run `npm run build` from the base directory and publish the `dist` folder.

The `netlify.toml` in this directory also defines the same build command, publish path, and SPA redirects.
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,
},
},
])
42 changes: 16 additions & 26 deletions src/problem2/index.html
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
<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" />
<meta name="description" content="Problem 2 — Fancy Form: currency swap form with live rates, token selector, slippage settings, i18n (React, TypeScript, Vite, Ant Design)." />
<title>Currency Swap | Fancy Form</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
15 changes: 15 additions & 0 deletions src/problem2/netlify.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Netlify build config — Problem 2 (Currency Swap)
# When repo root is this repo, set Base directory in Netlify UI to: src/problem2

[build]
command = "npm run build"
publish = "dist"

# SPA: serve index.html for all routes
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

[build.environment]
NODE_VERSION = "20"
Loading