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 `