From 0ee730e6602c89db71ca817e68220004c4a81668 Mon Sep 17 00:00:00 2001 From: AvstormvA Date: Sun, 10 May 2026 09:30:35 +0700 Subject: [PATCH] [Fullstack]: Nguyen Tien Dong --- .gitignore | 52 + package-lock.json | 6 + src/problem1/sum-to-n.js | 27 + src/problem2/README.md | 129 + src/problem2/components.json | 24 + src/problem2/index.html | 29 +- src/problem2/package-lock.json | 9560 +++++++++++++++++ src/problem2/package.json | 37 + src/problem2/public/vite.svg | 9 + src/problem2/src/App.tsx | 49 + src/problem2/src/components/SwapForm.tsx | 589 + src/problem2/src/components/TokenIcon.tsx | 40 + src/problem2/src/components/ui/button.tsx | 67 + src/problem2/src/components/ui/card.tsx | 103 + src/problem2/src/components/ui/input.tsx | 23 + src/problem2/src/components/ui/label.tsx | 22 + src/problem2/src/components/ui/select.tsx | 199 + src/problem2/src/index.css | 207 + src/problem2/src/lib/amountInput.ts | 49 + src/problem2/src/lib/mockBalance.ts | 26 + src/problem2/src/lib/prices.ts | 67 + src/problem2/src/lib/utils.ts | 6 + src/problem2/src/main.tsx | 15 + src/problem2/src/vite-env.d.ts | 1 + src/problem2/style.css | 8 - src/problem2/tsconfig.json | 24 + src/problem2/vite.config.ts | 42 + src/problem3/SOLUTION.md | 122 + src/problem4/.gitignore | 3 + src/problem4/README.md | 131 + src/problem4/package-lock.json | 2046 ++++ src/problem4/package.json | 26 + src/problem4/src/app.ts | 31 + src/problem4/src/db.ts | 26 + src/problem4/src/index.ts | 9 + src/problem4/src/openapi.ts | 217 + .../src/repositories/itemsRepository.ts | 100 + src/problem4/src/routes/items.ts | 94 + src/problem4/src/types/item.ts | 26 + src/problem4/tsconfig.json | 17 + src/problem5/README.md | 269 + 41 files changed, 14499 insertions(+), 28 deletions(-) create mode 100644 .gitignore create mode 100644 package-lock.json create mode 100644 src/problem1/sum-to-n.js create mode 100644 src/problem2/README.md create mode 100644 src/problem2/components.json create mode 100644 src/problem2/package-lock.json create mode 100644 src/problem2/package.json create mode 100644 src/problem2/public/vite.svg create mode 100644 src/problem2/src/App.tsx 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/ui/button.tsx create mode 100644 src/problem2/src/components/ui/card.tsx create mode 100644 src/problem2/src/components/ui/input.tsx create mode 100644 src/problem2/src/components/ui/label.tsx create mode 100644 src/problem2/src/components/ui/select.tsx create mode 100644 src/problem2/src/index.css create mode 100644 src/problem2/src/lib/amountInput.ts create mode 100644 src/problem2/src/lib/mockBalance.ts create mode 100644 src/problem2/src/lib/prices.ts create mode 100644 src/problem2/src/lib/utils.ts create mode 100644 src/problem2/src/main.tsx create mode 100644 src/problem2/src/vite-env.d.ts delete mode 100644 src/problem2/style.css create mode 100644 src/problem2/tsconfig.json create mode 100644 src/problem2/vite.config.ts create mode 100644 src/problem3/SOLUTION.md create mode 100644 src/problem4/.gitignore create mode 100644 src/problem4/README.md create mode 100644 src/problem4/package-lock.json create mode 100644 src/problem4/package.json create mode 100644 src/problem4/src/app.ts create mode 100644 src/problem4/src/db.ts create mode 100644 src/problem4/src/index.ts create mode 100644 src/problem4/src/openapi.ts create mode 100644 src/problem4/src/repositories/itemsRepository.ts create mode 100644 src/problem4/src/routes/items.ts create mode 100644 src/problem4/src/types/item.ts create mode 100644 src/problem4/tsconfig.json create mode 100644 src/problem5/README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..83fc8d0788 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# Dependencies (any folder named node_modules) +node_modules/ +**/node_modules/ + +# Build / output +dist/ +build/ +out/ +.next/ +.nuxt/ +.svelte-kit/ + +# Environment and secrets +.env +.env.* +!.env.example + +# Logs and debug +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Test / coverage +coverage/ +.nyc_output/ +*.lcov + +# Caches +.cache/ +.parcel-cache/ +.turbo/ +*.tsbuildinfo +.eslintcache +.stylelintcache + +# OS +.DS_Store +Thumbs.db +Desktop.ini + +# Editor / IDE +.idea/ +*.swp +*.swo +*~ + +# Optional tooling +.vercel/ +.netlify/ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..8f010e232c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "code-challenge", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/src/problem1/sum-to-n.js b/src/problem1/sum-to-n.js new file mode 100644 index 0000000000..234f14084b --- /dev/null +++ b/src/problem1/sum-to-n.js @@ -0,0 +1,27 @@ +// a. for loop +var sum_to_n_a = function (n) { + if (n < 1) { + return 0; + } + var total = 0; + for (var i = 1; i <= n; i++) { + total += i; + } + return total; +}; + +// b. formula +var sum_to_n_b = function (n) { + if (n < 1) { + return 0; + } + return (n * (n + 1)) / 2; +}; + +// c. recursion +var sum_to_n_c = function (n) { + if (n < 1) { + return 0; + } + return n + sum_to_n_c(n - 1); +}; diff --git a/src/problem2/README.md b/src/problem2/README.md new file mode 100644 index 0000000000..a83fece3af --- /dev/null +++ b/src/problem2/README.md @@ -0,0 +1,129 @@ +# Problem 2 — Fancy Form (Currency Swap) + +A polished, single-page **token swap** UI built as a front-end demo. Pick two assets, type an amount, and see the converted output computed from live USD prices. Submission is simulated with a short delay — there is no real backend or wallet. + +Live data comes from the public Switcheo interview feed: + +- Prices: +- Icons: + +## Stack + +| Piece | Role | +| ----- | ---- | +| [React 18](https://react.dev/) + [TypeScript](https://www.typescriptlang.org/) | UI + types | +| [Vite 5](https://vitejs.dev/) | Dev server / bundler | +| [Tailwind CSS v4](https://tailwindcss.com/) (`@tailwindcss/vite`) | Styling | +| [shadcn/ui](https://ui.shadcn.com/) + [Radix UI](https://www.radix-ui.com/) | Accessible primitives (Select, Label, Slot, …) | +| [framer-motion](https://www.framer.com/motion/) | Micro-interactions and transitions | +| [lucide-react](https://lucide.dev/) | Icon set | +| [`@fontsource-variable/geist`](https://fontsource.org/fonts/geist) | Variable font | + +## Requirements + +- Node.js 20+ (LTS recommended) +- npm + +## Install and run + +```bash +cd src/problem2 +npm install +``` + +**Development** (Vite dev server with HMR): + +```bash +npm run dev +``` + +Then open the printed URL (typically ). + +**Production build**: + +```bash +npm run build +npm run preview +``` + +`preview` serves the built bundle locally so you can sanity-check the production output. + +## How the prices feed is wired + +To avoid CORS issues during local development, Vite proxies the prices request: + +```ts +// vite.config.ts +server: { + proxy: { + "/prices.json": { + target: "https://interview.switcheo.com", + changeOrigin: true, + secure: true, + }, + }, +} +``` + +In **dev** the app fetches `/prices.json` (proxied). In **production** it fetches `https://interview.switcheo.com/prices.json` directly. See `src/lib/prices.ts`. + +The feed contains multiple rows per currency on different dates; only the **most recent** row per currency is kept (see `dedupeLatestByCurrency`). + +## What the form does + +- **Token pickers** with searchable Radix-powered select, showing icon, symbol and current USD price for each token. +- **Amount input** with a permissive parser (`src/lib/amountInput.ts`) that: + - strips thousands separators, + - rejects negatives, multiple decimal points, and non-numeric input, + - caps fractional digits (12 by default) and reports a helpful error message. +- **Live conversion** using `convertAmount(amount, fromPriceUsd, toPriceUsd)` — `(amount × fromPrice) / toPrice`. +- **Reverse button** swaps the two tokens and carries the receive-side amount back into the send field. +- **Mock balances** (`src/lib/mockBalance.ts`) per currency so the form can validate "exceeds balance" without a real wallet. +- **Validation surfaces inline**: same token on both sides, invalid number, or amount above the demo balance — all rendered under the input and reflected in `aria-invalid` and the disabled state of the submit button. +- **Submit** is a simulated async action: a 1.8 s delay, then a success toast-style banner with the executed pair and amounts. Nothing is sent anywhere. +- **Loading and error states** handle the price-fetch lifecycle, including a Retry button if the feed fails or returns fewer than two priced tokens. +- **Smart defaults**: prefers `ETH → USDC` when both exist; otherwise falls back to the first two priced tokens. + +## Accessibility and UX touches + +- Radix primitives provide keyboard navigation and focus management out of the box. +- Inputs are paired with `