From 22d09227e36d235cb3e7c9d67b853306f3da4993 Mon Sep 17 00:00:00 2001 From: ThangLCoders Date: Mon, 9 Mar 2026 18:44:22 +0700 Subject: [PATCH 1/4] feat(problem1): sum 1 to n with 3 implementations and tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add rules.md: senior engineering standards for the code challenge. - Implement sum_to_n in three ways: A) Closed-form n(n+1)/2 — O(1) time/space. B) For-loop accumulation — O(n) time, O(1) space. C) Recursion with base case n<1 — O(n) time, O(n) stack. - Handle edge cases: n<1 returns 0, non-integer via Math.floor. - Add sum_to_n.test.js: normal cases, n=0, negative, non-integer. - All logic pure; exports for Node. Made-with: Cursor --- rules.md | 294 ++++++++++++++++++++++++++++++++++ src/problem1/sum_to_n.js | 57 +++++++ src/problem1/sum_to_n.test.js | 51 ++++++ 3 files changed, 402 insertions(+) create mode 100644 rules.md create mode 100644 src/problem1/sum_to_n.js create mode 100644 src/problem1/sum_to_n.test.js diff --git a/rules.md b/rules.md new file mode 100644 index 0000000000..ad14646228 --- /dev/null +++ b/rules.md @@ -0,0 +1,294 @@ +# Senior Engineering Rules + +You are a Senior Software Engineer with 10+ years of experience. +All code generated must follow production-grade standards. + +--- + +# 1. Engineering Mindset + +Always think like a senior engineer working on a large-scale production system. + +Before writing code: + +1. Understand the problem clearly +2. Identify constraints +3. Consider edge cases +4. Design the architecture +5. Evaluate performance implications + +Never jump directly to code without reasoning. + +--- + +# 2. Code Quality Standards + +All code must be: + +- Clean +- Maintainable +- Scalable +- Readable +- Production-ready + +Follow these principles: + +- SOLID principles +- DRY (Don't Repeat Yourself) +- KISS (Keep It Simple) +- Separation of Concerns + +Avoid: + +- unnecessary abstractions +- duplicated logic +- deeply nested code +- unclear naming + +--- + +# 3. Naming Conventions + +Use clear and descriptive names. + +Bad: + +data +temp +x + +Good: + +userProfile +paymentRequest +gameSessionState + +Functions must describe behavior: + +calculateTotalBet() +fetchUserWallet() +validatePaymentRequest() + +--- + +# 4. File Structure + +Prefer scalable architecture. + +Example frontend structure: + +src + ├ components + ├ features + ├ hooks + ├ services + ├ store + ├ utils + └ types + +Rules: + +- Components must be small and reusable +- Business logic must not live in UI components +- API logic must be in services +- Shared logic goes to hooks + +--- + +# 5. React / Next.js Best Practices + +Follow modern React patterns. + +Prefer: + +- functional components +- hooks +- server components when possible +- minimal client state + +Avoid: + +- unnecessary re-renders +- heavy logic in components +- prop drilling (use context or state management) + +--- + +# 6. State Management + +Follow these rules: + +Server state: +Use TanStack Query + +Client state: +Use minimal state management + +Avoid global state unless necessary. + +--- + +# 7. Performance Rules + +Always consider performance. + +Optimize: + +- unnecessary renders +- large lists +- API calls +- heavy calculations + +Prefer: + +memoization +lazy loading +code splitting +virtualization for large lists + +Example tools: + +React.memo +useMemo +useCallback +dynamic imports + +--- + +# 8. Error Handling + +Never ignore errors. + +Handle: + +API errors +network failures +unexpected states + +Provide safe fallbacks. + +Example: + +try/catch +error boundaries +safe null checks + +--- + +# 9. Type Safety + +Prefer strong typing. + +Rules: + +- Avoid `any` +- Create reusable types +- Use generics when needed + +Example: + +type User = { + id: string + email: string + walletBalance: number +} + +--- + +# 10. API Design + +API interactions must be: + +- isolated +- typed +- reusable + +Example: + +services/ + paymentService.ts + userService.ts + +Avoid calling fetch/axios directly in components. + +--- + +# 11. Testing Mindset + +Always consider testability. + +Code must be easy to test. + +Functions should be: + +- pure when possible +- deterministic +- modular + +Include tests for: + +- normal cases +- edge cases +- invalid input + +--- + +# 12. Refactoring + +When modifying code: + +- preserve existing functionality +- reduce complexity +- improve readability +- remove dead code + +Never introduce breaking changes without reason. + +--- + +# 13. Code Review Mindset + +Before finishing: + +Check: + +- readability +- edge cases +- performance +- maintainability + +Ask: + +Would this scale to 100k users? +Would another engineer understand this easily? + +--- + +# 14. Algorithm Problems + +When solving coding challenges: + +1. Explain the approach +2. Discuss time complexity +3. Discuss space complexity +4. Provide optimized solution +5. Write clean code + +--- + +# 15. Security Awareness + +Avoid: + +- exposing sensitive data +- insecure API usage +- trusting client input + +Always validate inputs. + +--- + +# Final Rule + +Write code as if it will run in production with millions of users. +Prioritize clarity, maintainability, and performance. \ No newline at end of file diff --git a/src/problem1/sum_to_n.js b/src/problem1/sum_to_n.js new file mode 100644 index 0000000000..4e93b97f6b --- /dev/null +++ b/src/problem1/sum_to_n.js @@ -0,0 +1,57 @@ +/** + * Problem: Summation from 1 to n. + * Input: n — integer (result assumed < Number.MAX_SAFE_INTEGER). + * Output: 1 + 2 + ... + n (e.g. sum_to_n(5) === 15). + * + * Edge cases handled: + * - n < 1: returns 0 (empty sum). + * - Non-integer n: truncated via Math.floor for predictable behavior. + */ + +/** + * Implementation A: Closed-form formula. + * Uses the identity: 1 + 2 + ... + n = n * (n + 1) / 2. + * + * Time: O(1) + * Space: O(1) + */ +var sum_to_n_a = function (n) { + var k = Math.floor(Number(n)); + if (k < 1) return 0; + return (k * (k + 1)) / 2; +}; + +/** + * Implementation B: Iterative loop (for). + * Accumulates the sum in a single variable. + * + * Time: O(n) + * Space: O(1) + */ +var sum_to_n_b = function (n) { + var k = Math.floor(Number(n)); + if (k < 1) return 0; + var sum = 0; + for (var i = 1; i <= k; i++) { + sum += i; + } + return sum; +}; + +/** + * Implementation C: Recursion. + * Base case: n < 1 → 0. Otherwise: n + sum_to_n_c(n - 1). + * + * Time: O(n) + * Space: O(n) — call stack depth + */ +var sum_to_n_c = function (n) { + var k = Math.floor(Number(n)); + if (k < 1) return 0; + return k + sum_to_n_c(k - 1); +}; + +// Export for tests and reuse +if (typeof module !== "undefined" && module.exports) { + module.exports = { sum_to_n_a, sum_to_n_b, sum_to_n_c }; +} diff --git a/src/problem1/sum_to_n.test.js b/src/problem1/sum_to_n.test.js new file mode 100644 index 0000000000..70c53141bd --- /dev/null +++ b/src/problem1/sum_to_n.test.js @@ -0,0 +1,51 @@ +/** + * Tests for sum_to_n implementations. + * Run with: node sum_to_n.test.js + */ + +var assert = require("assert"); +var { sum_to_n_a, sum_to_n_b, sum_to_n_c } = require("./sum_to_n.js"); + +var implementations = [ + { name: "sum_to_n_a", fn: sum_to_n_a }, + { name: "sum_to_n_b", fn: sum_to_n_b }, + { name: "sum_to_n_c", fn: sum_to_n_c }, +]; + +function runTests() { + var passed = 0; + var failed = 0; + + function test(description, n, expected) { + implementations.forEach(function (impl) { + try { + var result = impl.fn(n); + assert.strictEqual( + result, + expected, + impl.name + "(" + n + ") expected " + expected + ", got " + result + ); + passed++; + } catch (err) { + console.error("FAIL " + impl.name + ": " + description + " — " + err.message); + failed++; + } + }); + } + + // Normal cases + test("sum to 5", 5, 15); + test("sum to 1", 1, 1); + test("sum to 10", 10, 55); + test("sum to 100", 100, 5050); + + // Edge cases + test("n = 0", 0, 0); + test("n negative", -5, 0); + test("non-integer (floor)", 5.9, 15); + + console.log("Tests: " + passed + " passed, " + failed + " failed"); + process.exit(failed > 0 ? 1 : 0); +} + +runTests(); From 3888cb3bbaa2b3aa9af2c88feea1077b46aa7e39 Mon Sep 17 00:00:00 2001 From: ThangLCoders Date: Mon, 9 Mar 2026 18:44:33 +0700 Subject: [PATCH 2/4] feat(problem2): currency swap form with Vite, React, TypeScript - Replace template with Vite + React app; logic in TypeScript, UI in JSX. - Data: Switcheo prices.json and token-icons repo; dedupe by currency, omit no-price. - Architecture: services (priceService), hooks (useTokensWithPrices, useSwapForm), utils (exchange, validation, tokenIcon, imagePreload), types (Token, PriceRecord). - UI: SwapCard, TokenSelect, TokenIcon; validation, swap direction, rate display. - Preload token images on load; fallback icon on image error; toast on success/error with swap details (amount from/to). English toast messages. Made-with: Cursor --- src/problem2/{script.js => .keep} | 0 src/problem2/App.jsx | 10 + src/problem2/README.md | 46 + src/problem2/index.css | 51 + src/problem2/index.html | 41 +- src/problem2/main.jsx | 10 + src/problem2/package-lock.json | 1729 +++++++++++++++++ src/problem2/package.json | 22 + src/problem2/src/components/SwapCard.jsx | 135 ++ src/problem2/src/components/SwapForm.jsx | 75 + .../src/components/SwapFormStyles.jsx | 390 ++++ src/problem2/src/components/Toast.jsx | 27 + src/problem2/src/components/TokenIcon.jsx | 24 + src/problem2/src/components/TokenSelect.jsx | 64 + src/problem2/src/hooks/useSwapForm.ts | 100 + src/problem2/src/hooks/useTokensWithPrices.ts | 35 + src/problem2/src/services/priceService.ts | 30 + src/problem2/src/types/index.ts | 11 + src/problem2/src/utils/exchange.ts | 15 + src/problem2/src/utils/imagePreload.ts | 14 + src/problem2/src/utils/tokenIcon.ts | 7 + src/problem2/src/utils/validation.ts | 7 + src/problem2/style.css | 8 - src/problem2/tsconfig.json | 23 + src/problem2/vite.config.js | 7 + 25 files changed, 2847 insertions(+), 34 deletions(-) rename src/problem2/{script.js => .keep} (100%) create mode 100644 src/problem2/App.jsx create mode 100644 src/problem2/README.md create mode 100644 src/problem2/index.css create mode 100644 src/problem2/main.jsx create mode 100644 src/problem2/package-lock.json create mode 100644 src/problem2/package.json create mode 100644 src/problem2/src/components/SwapCard.jsx create mode 100644 src/problem2/src/components/SwapForm.jsx create mode 100644 src/problem2/src/components/SwapFormStyles.jsx create mode 100644 src/problem2/src/components/Toast.jsx create mode 100644 src/problem2/src/components/TokenIcon.jsx create mode 100644 src/problem2/src/components/TokenSelect.jsx create mode 100644 src/problem2/src/hooks/useSwapForm.ts create mode 100644 src/problem2/src/hooks/useTokensWithPrices.ts create mode 100644 src/problem2/src/services/priceService.ts create mode 100644 src/problem2/src/types/index.ts create mode 100644 src/problem2/src/utils/exchange.ts create mode 100644 src/problem2/src/utils/imagePreload.ts create mode 100644 src/problem2/src/utils/tokenIcon.ts create mode 100644 src/problem2/src/utils/validation.ts delete mode 100644 src/problem2/style.css create mode 100644 src/problem2/tsconfig.json create mode 100644 src/problem2/vite.config.js diff --git a/src/problem2/script.js b/src/problem2/.keep similarity index 100% rename from src/problem2/script.js rename to src/problem2/.keep diff --git a/src/problem2/App.jsx b/src/problem2/App.jsx new file mode 100644 index 0000000000..d3864f5b0d --- /dev/null +++ b/src/problem2/App.jsx @@ -0,0 +1,10 @@ +import React from "react"; +import { SwapForm } from "./src/components/SwapForm"; + +export default function App() { + return ( +
+ +
+ ); +} diff --git a/src/problem2/README.md b/src/problem2/README.md new file mode 100644 index 0000000000..2abf049004 --- /dev/null +++ b/src/problem2/README.md @@ -0,0 +1,46 @@ +# Problem 2: Currency Swap Form + +Form đổi token dùng **Vite** + **React**. Logic viết bằng **TypeScript** (services, hooks, utils, types); UI giữ bằng JSX trong `src/components`. + +## Chạy + +```bash +cd src/problem2 +npm install +npm run dev +``` + +Mở http://localhost:5173 + +## Nguồn dữ liệu + +- **Giá token:** https://interview.switcheo.com/prices.json +- **Icon token:** https://github.com/Switcheo/token-icons/tree/main/tokens (vd: SWTH.svg) + +--- + +## Cấu trúc (components, logic = TS) + +``` +src/ +├── components/ # UI (JSX) +│ ├── SwapForm.jsx +│ ├── SwapCard.jsx +│ ├── TokenSelect.jsx +│ ├── TokenIcon.jsx +│ └── SwapFormStyles.jsx +├── hooks/ # Logic (TypeScript) +│ ├── useTokensWithPrices.ts +│ └── useSwapForm.ts +├── services/ # API (TypeScript) +│ └── priceService.ts +├── utils/ # Hàm thuần (TypeScript) +│ ├── tokenIcon.ts +│ ├── exchange.ts +│ └── validation.ts +└── types/ # Định nghĩa type (TypeScript) + └── index.ts +``` + +- **Logic (TS):** `types`, `services`, `hooks`, `utils` — toàn bộ file `.ts`. +- **UI (JSX):** `components` — file `.jsx`, chỉ render và nhận props, gọi hooks từ container. diff --git a/src/problem2/index.css b/src/problem2/index.css new file mode 100644 index 0000000000..5038f0d445 --- /dev/null +++ b/src/problem2/index.css @@ -0,0 +1,51 @@ +:root { + --bg-primary: #0d0f14; + --bg-card: #151922; + --bg-input: #1a1e28; + --border: #2a3142; + --text-primary: #e6e8ec; + --text-secondary: #8b92a5; + --accent: #00d395; + --accent-dim: #00a876; + --error: #ff5c5c; + --radius: 14px; + --radius-sm: 10px; + --shadow: 0 12px 40px rgba(0, 0, 0, 0.4); + --font-sans: "DM Sans", -apple-system, BlinkMacSystemFont, sans-serif; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + background: var(--bg-primary); + color: var(--text-primary); + font-family: var(--font-sans); + font-size: 16px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +.app { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + width: 100%; +} + +#root { + width: 100%; + max-width: 520px; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; +} diff --git a/src/problem2/index.html b/src/problem2/index.html index 4058a68bff..6d53a41a31 100644 --- a/src/problem2/index.html +++ b/src/problem2/index.html @@ -1,27 +1,16 @@ - - - - - Fancy Form - - - - - - - - -
-
Swap
- - - - - - - -
- - - + + + + + + Currency Swap + + + + + + +
+ + diff --git a/src/problem2/main.jsx b/src/problem2/main.jsx new file mode 100644 index 0000000000..f18b0f0d14 --- /dev/null +++ b/src/problem2/main.jsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root")).render( + + + +); diff --git a/src/problem2/package-lock.json b/src/problem2/package-lock.json new file mode 100644 index 0000000000..50c1daff37 --- /dev/null +++ b/src/problem2/package-lock.json @@ -0,0 +1,1729 @@ +{ + "name": "currency-swap-form", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "currency-swap-form", + "version": "1.0.0", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.2.1", + "typescript": "^5.3.0", + "vite": "^5.4.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001777", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", + "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/src/problem2/package.json b/src/problem2/package.json new file mode 100644 index 0000000000..3c89a90899 --- /dev/null +++ b/src/problem2/package.json @@ -0,0 +1,22 @@ +{ + "name": "currency-swap-form", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.2.1", + "typescript": "^5.3.0", + "vite": "^5.4.0" + } +} diff --git a/src/problem2/src/components/SwapCard.jsx b/src/problem2/src/components/SwapCard.jsx new file mode 100644 index 0000000000..a5801e6acf --- /dev/null +++ b/src/problem2/src/components/SwapCard.jsx @@ -0,0 +1,135 @@ +import React from "react"; +import { TokenSelect } from "./TokenSelect"; + +export function SwapCard({ + title = "Swap", + tokens = [], + tokensLoading, + tokensError, + onRetry, + fromToken, + toToken, + fromAmountRaw, + fromAmountError, + toAmount, + exchangeRate, + submitState, + canSubmit, + sameTokenError, + onFromTokenSelect, + onToTokenSelect, + onFromAmountChange, + onSwapDirection, + onSubmit, +}) { + return ( +
+

{title}

+ + {tokensError && ( +
+ {tokensError} + +
+ )} + + {tokensLoading && ( +
Loading tokens…
+ )} + + {!tokensLoading && ( +
+
+ + + onFromAmountChange(e.target.value)} + className={fromAmountError ? "swap-input swap-input-error" : "swap-input"} + aria-invalid={!!fromAmountError} + aria-describedby={fromAmountError ? "from-amount-error" : undefined} + disabled={tokensLoading} + /> + {fromAmountError && ( + + {fromAmountError} + + )} +
+ + + +
+ + + 0 ? toAmount.toFixed(6) : ""} + className="swap-input swap-input-readonly" + aria-readonly="true" + disabled + /> + {exchangeRate != null && fromToken && toToken && ( +

+ 1 {fromToken.currency} ≈ {exchangeRate.toFixed(6)} {toToken.currency} +

+ )} +
+ + {sameTokenError && ( +

+ Select different tokens for from and to. +

+ )} + + +
+ )} +
+ ); +} diff --git a/src/problem2/src/components/SwapForm.jsx b/src/problem2/src/components/SwapForm.jsx new file mode 100644 index 0000000000..13d2575234 --- /dev/null +++ b/src/problem2/src/components/SwapForm.jsx @@ -0,0 +1,75 @@ +import React, { useState, useEffect, useRef } from "react"; +import { useTokensWithPrices } from "../hooks/useTokensWithPrices"; +import { useSwapForm } from "../hooks/useSwapForm"; +import { SwapCard } from "./SwapCard"; +import { SwapFormStyles } from "./SwapFormStyles"; +import { Toast } from "./Toast"; + +function formatAmount(num) { + if (num >= 1e6) return num.toExponential(2); + if (num >= 1) return num.toFixed(4); + if (num > 0) return num.toFixed(6); + return "0"; +} + +export function SwapForm() { + const { tokens, loading: tokensLoading, error: tokensError, refetch } = useTokensWithPrices(); + const swap = useSwapForm(tokens); + const [toast, setToast] = useState(null); + const prevSubmitState = useRef(swap.submitState); + + useEffect(() => { + if (swap.submitState === "success" && prevSubmitState.current !== "success") { + const s = swap.lastSwapSummary; + const msg = s + ? `Swapped ${formatAmount(s.fromAmount)} ${s.fromCurrency} for ${formatAmount(s.toAmount)} ${s.toCurrency}` + : "Swap successful"; + setToast({ message: msg, type: "success" }); + } else if (swap.submitState === "error" && prevSubmitState.current !== "error") { + const s = swap.lastSwapSummary; + const msg = s + ? `Swap failed: ${formatAmount(s.fromAmount)} ${s.fromCurrency} → ${formatAmount(s.toAmount)} ${s.toCurrency}. Please try again.` + : "Swap failed. Please try again."; + setToast({ message: msg, type: "error" }); + } + prevSubmitState.current = swap.submitState; + }, [swap.submitState, swap.lastSwapSummary]); + + return ( + <> + + {toast && ( +
+ setToast(null)} + duration={4000} + /> +
+ )} + + + ); +} diff --git a/src/problem2/src/components/SwapFormStyles.jsx b/src/problem2/src/components/SwapFormStyles.jsx new file mode 100644 index 0000000000..2f92f10513 --- /dev/null +++ b/src/problem2/src/components/SwapFormStyles.jsx @@ -0,0 +1,390 @@ +import React from "react"; + +export function SwapFormStyles() { + return ( + + ); +} diff --git a/src/problem2/src/components/Toast.jsx b/src/problem2/src/components/Toast.jsx new file mode 100644 index 0000000000..2e41a7a19b --- /dev/null +++ b/src/problem2/src/components/Toast.jsx @@ -0,0 +1,27 @@ +import React, { useEffect } from "react"; + +export function Toast({ message, type = "info", onDismiss, duration = 4000 }) { + useEffect(() => { + if (!duration || !onDismiss) return; + const id = setTimeout(onDismiss, duration); + return () => clearTimeout(id); + }, [duration, onDismiss]); + + return ( +
+ {message} + +
+ ); +} diff --git a/src/problem2/src/components/TokenIcon.jsx b/src/problem2/src/components/TokenIcon.jsx new file mode 100644 index 0000000000..8e2e865517 --- /dev/null +++ b/src/problem2/src/components/TokenIcon.jsx @@ -0,0 +1,24 @@ +import React, { useState } from "react"; + +export function TokenIcon({ src, symbol, className = "", priority = false }) { + const [imageError, setImageError] = useState(false); + const showFallback = !src || imageError; + + return ( + + {showFallback ? ( + {symbol?.slice(0, 1) ?? "?"} + ) : ( + setImageError(true)} + /> + )} + + ); +} diff --git a/src/problem2/src/components/TokenSelect.jsx b/src/problem2/src/components/TokenSelect.jsx new file mode 100644 index 0000000000..93810c096e --- /dev/null +++ b/src/problem2/src/components/TokenSelect.jsx @@ -0,0 +1,64 @@ +import React, { useState, useRef, useEffect } from "react"; +import { TokenIcon } from "./TokenIcon"; + +export function TokenSelect({ tokens, selectedToken, onSelect, label, disabled }) { + const [open, setOpen] = useState(false); + const listRef = useRef(null); + + useEffect(() => { + if (!open) return; + const handleClickOutside = (e) => { + if (listRef.current && !listRef.current.contains(e.target)) setOpen(false); + }; + document.addEventListener("click", handleClickOutside); + return () => document.removeEventListener("click", handleClickOutside); + }, [open]); + + return ( +
+ + + {open && ( +
    + {tokens.map((token) => ( +
  • + +
  • + ))} +
+ )} +
+ ); +} diff --git a/src/problem2/src/hooks/useSwapForm.ts b/src/problem2/src/hooks/useSwapForm.ts new file mode 100644 index 0000000000..0b2ea796f3 --- /dev/null +++ b/src/problem2/src/hooks/useSwapForm.ts @@ -0,0 +1,100 @@ +import { useState, useMemo, useCallback, FormEvent } from "react"; +import { convertAmount, getExchangeRate } from "../utils/exchange"; +import { validateAmount } from "../utils/validation"; +import type { Token } from "../types"; + +const INITIAL_FROM_AMOUNT = ""; +const SUBMIT_DELAY_MS = 1800; + +export type SubmitState = "idle" | "loading" | "success" | "error"; + +export interface LastSwapSummary { + fromAmount: number; + fromCurrency: string; + toAmount: number; + toCurrency: string; +} + +export function useSwapForm(_tokens: Token[]) { + const [fromToken, setFromToken] = useState(null); + const [toToken, setToToken] = useState(null); + const [fromAmountRaw, setFromAmountRaw] = useState(INITIAL_FROM_AMOUNT); + const [submitState, setSubmitState] = useState("idle"); + const [lastSwapSummary, setLastSwapSummary] = useState(null); + + const fromAmount = fromAmountRaw === "" ? 0 : Number(fromAmountRaw); + const fromAmountError = validateAmount(fromAmountRaw); + + const toAmount = useMemo(() => { + if (!fromToken || !toToken || fromAmount <= 0) return 0; + return convertAmount(fromAmount, fromToken, toToken); + }, [fromToken, toToken, fromAmount]); + + const exchangeRate = useMemo(() => { + if (!fromToken || !toToken) return null; + return getExchangeRate(fromToken, toToken); + }, [fromToken, toToken]); + + const handleSwapDirection = useCallback(() => { + setFromToken(toToken); + setToToken(fromToken); + setFromAmountRaw(toAmount > 0 ? String(toAmount) : ""); + }, [fromToken, toToken, toAmount]); + + const canSubmit = + !!fromToken && + !!toToken && + fromToken.currency !== toToken.currency && + fromAmount > 0 && + !fromAmountError && + submitState !== "loading"; + + const handleSubmit = useCallback( + async (e: FormEvent) => { + e.preventDefault(); + if (!canSubmit || !fromToken || !toToken) return; + const fromAmt = fromAmountRaw === "" ? 0 : Number(fromAmountRaw); + const toAmt = convertAmount(fromAmt, fromToken, toToken); + setSubmitState("loading"); + try { + await new Promise((r) => setTimeout(r, SUBMIT_DELAY_MS)); + setLastSwapSummary({ + fromAmount: fromAmt, + fromCurrency: fromToken.currency, + toAmount: toAmt, + toCurrency: toToken.currency, + }); + setSubmitState("success"); + setFromAmountRaw(INITIAL_FROM_AMOUNT); + setTimeout(() => setSubmitState("idle"), 2000); + } catch { + setLastSwapSummary({ + fromAmount: fromAmt, + fromCurrency: fromToken.currency, + toAmount: toAmt, + toCurrency: toToken.currency, + }); + setSubmitState("error"); + setTimeout(() => setSubmitState("idle"), 3000); + } + }, + [canSubmit, fromToken, toToken, fromAmountRaw] + ); + + return { + fromToken, + toToken, + fromAmountRaw, + fromAmountError, + toAmount, + exchangeRate, + submitState, + canSubmit, + lastSwapSummary, + setFromToken, + setToToken, + setFromAmountRaw, + handleSwapDirection, + handleSubmit, + }; +} diff --git a/src/problem2/src/hooks/useTokensWithPrices.ts b/src/problem2/src/hooks/useTokensWithPrices.ts new file mode 100644 index 0000000000..7def36ca4f --- /dev/null +++ b/src/problem2/src/hooks/useTokensWithPrices.ts @@ -0,0 +1,35 @@ +import { useState, useEffect, useCallback } from "react"; +import { fetchTokensWithPrices } from "../services/priceService"; +import { preloadTokenImages } from "../utils/imagePreload"; +import type { Token } from "../types"; + +export function useTokensWithPrices(): { + tokens: Token[]; + loading: boolean; + error: string | null; + refetch: () => Promise; +} { + const [tokens, setTokens] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const refetch = useCallback(async () => { + setLoading(true); + setError(null); + try { + const data = await fetchTokensWithPrices(); + setTokens(data); + preloadTokenImages(data.map((t) => t.imageUrl)); + } catch (e) { + setError(e instanceof Error ? e.message : "Something went wrong"); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + refetch(); + }, [refetch]); + + return { tokens, loading, error, refetch }; +} diff --git a/src/problem2/src/services/priceService.ts b/src/problem2/src/services/priceService.ts new file mode 100644 index 0000000000..d5b2273df4 --- /dev/null +++ b/src/problem2/src/services/priceService.ts @@ -0,0 +1,30 @@ +import type { PriceRecord, Token } from "../types"; +import { getTokenImageUrl } from "../utils/tokenIcon"; + +const PRICES_URL = "https://interview.switcheo.com/prices.json"; + +export async function fetchTokensWithPrices(): Promise { + const res = await fetch(PRICES_URL); + if (!res.ok) { + throw new Error("Failed to load token prices. Please try again."); + } + const data: PriceRecord[] = await res.json(); + + const byCurrency = new Map(); + for (const row of data) { + if (row.price == null || row.price <= 0) continue; + const existing = byCurrency.get(row.currency); + if ( + !existing || + new Date(row.date).getTime() > new Date(existing.date).getTime() + ) { + byCurrency.set(row.currency, row); + } + } + + return Array.from(byCurrency.values()).map((row) => ({ + currency: row.currency, + price: row.price, + imageUrl: getTokenImageUrl(row.currency), + })); +} diff --git a/src/problem2/src/types/index.ts b/src/problem2/src/types/index.ts new file mode 100644 index 0000000000..71b7e82c78 --- /dev/null +++ b/src/problem2/src/types/index.ts @@ -0,0 +1,11 @@ +export interface PriceRecord { + currency: string; + date: string; + price: number; +} + +export interface Token { + currency: string; + price: number; + imageUrl: string; +} diff --git a/src/problem2/src/utils/exchange.ts b/src/problem2/src/utils/exchange.ts new file mode 100644 index 0000000000..9023849810 --- /dev/null +++ b/src/problem2/src/utils/exchange.ts @@ -0,0 +1,15 @@ +import type { Token } from "../types"; + +export function getExchangeRate(fromToken: Token, toToken: Token): number { + if (!fromToken?.price || !toToken?.price) return 0; + return fromToken.price / toToken.price; +} + +export function convertAmount( + amount: number, + fromToken: Token, + toToken: Token +): number { + const rate = getExchangeRate(fromToken, toToken); + return amount * rate; +} diff --git a/src/problem2/src/utils/imagePreload.ts b/src/problem2/src/utils/imagePreload.ts new file mode 100644 index 0000000000..4e51604b4d --- /dev/null +++ b/src/problem2/src/utils/imagePreload.ts @@ -0,0 +1,14 @@ +/** + * Preload token images in background so dropdown shows them from cache. + */ + +const preloadCache = new Set(); + +export function preloadTokenImages(urls: string[], limit = 20): void { + const toLoad = urls.filter(Boolean).filter((url) => !preloadCache.has(url)); + toLoad.slice(0, limit).forEach((url) => { + preloadCache.add(url); + const img = new Image(); + img.src = url; + }); +} diff --git a/src/problem2/src/utils/tokenIcon.ts b/src/problem2/src/utils/tokenIcon.ts new file mode 100644 index 0000000000..d76ed2bc2c --- /dev/null +++ b/src/problem2/src/utils/tokenIcon.ts @@ -0,0 +1,7 @@ +const TOKEN_ICONS_BASE = + "https://raw.githubusercontent.com/Switcheo/token-icons/main/tokens"; + +export function getTokenImageUrl(currency: string): string { + if (!currency || typeof currency !== "string") return ""; + return `${TOKEN_ICONS_BASE}/${encodeURIComponent(currency)}.svg`; +} diff --git a/src/problem2/src/utils/validation.ts b/src/problem2/src/utils/validation.ts new file mode 100644 index 0000000000..5f1348d870 --- /dev/null +++ b/src/problem2/src/utils/validation.ts @@ -0,0 +1,7 @@ +export function validateAmount(value: string): string | null { + if (value === "" || value == null) return null; + const num = Number(value); + if (Number.isNaN(num)) return "Enter a valid number"; + if (num <= 0) return "Amount must be greater than 0"; + return null; +} diff --git a/src/problem2/style.css b/src/problem2/style.css deleted file mode 100644 index 915af91c72..0000000000 --- a/src/problem2/style.css +++ /dev/null @@ -1,8 +0,0 @@ -body { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - min-width: 360px; - font-family: Arial, Helvetica, sans-serif; -} diff --git a/src/problem2/tsconfig.json b/src/problem2/tsconfig.json new file mode 100644 index 0000000000..1f9520c128 --- /dev/null +++ b/src/problem2/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src"] +} diff --git a/src/problem2/vite.config.js b/src/problem2/vite.config.js new file mode 100644 index 0000000000..86759bd80f --- /dev/null +++ b/src/problem2/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + root: ".", +}); From f0c62293abc80ebcddf3e5530f70fe7fd4f857f9 Mon Sep 17 00:00:00 2001 From: ThangLCoders Date: Mon, 9 Mar 2026 18:44:40 +0700 Subject: [PATCH 3/4] feat(problem3): WalletPage analysis and refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ANALYSIS.md: document 14 issues (bugs, performance, types, architecture). Fix wrong filter variable (lhsPriority→balancePriority), filter logic, missing blockchain on WalletBalance, useMemo deps, sort return 0, use formattedBalances for rows, stable keys, getPriority outside component, Blockchain union type, logic in hook/utils, error/loading handling. - Refactor: types (wallet.ts), utils (walletBalance.ts), hook (useSortedFormattedBalances), WalletPage UI-only. Aligns with rules.md. Made-with: Cursor --- src/problem3/ANALYSIS.md | 164 ++++++++++++++++++ src/problem3/src/components/WalletPage.tsx | 51 ++++++ .../src/hooks/useSortedFormattedBalances.ts | 17 ++ src/problem3/src/types/wallet.ts | 31 ++++ src/problem3/src/utils/walletBalance.ts | 57 ++++++ 5 files changed, 320 insertions(+) create mode 100644 src/problem3/ANALYSIS.md create mode 100644 src/problem3/src/components/WalletPage.tsx create mode 100644 src/problem3/src/hooks/useSortedFormattedBalances.ts create mode 100644 src/problem3/src/types/wallet.ts create mode 100644 src/problem3/src/utils/walletBalance.ts diff --git a/src/problem3/ANALYSIS.md b/src/problem3/ANALYSIS.md new file mode 100644 index 0000000000..7d5ea3c9b6 --- /dev/null +++ b/src/problem3/ANALYSIS.md @@ -0,0 +1,164 @@ +# Problem 3: WalletPage — Computational Inefficiencies & Anti-Patterns + +## 1. Issues and How to Improve + +### 1.1 Bug: Wrong variable in filter (`lhsPriority`) + +**Issue:** In the filter callback, the code checks `if (lhsPriority > -99)` but `lhsPriority` is never defined in that scope. The variable that was computed is `balancePriority`. + +**Impact:** Either a ReferenceError at runtime or (if `lhsPriority` exists elsewhere) incorrect filtering. + +**Fix:** Use `balancePriority` instead of `lhsPriority` in the condition. + +--- + +### 1.2 Bug: Inverted / unclear filter logic + +**Issue:** The filter returns `true` when `balance.amount <= 0`, so only zero or negative balances are kept. Typically we want to show balances that are meaningful (e.g. positive, or at least not negative). The condition is also nested incorrectly: we only consider amount when priority > -99, which keeps zero/negative balances for known blockchains. + +**Impact:** Wrong set of balances is displayed (likely only empty/negative ones). + +**Fix:** Clarify requirements. Usually we want to **exclude** zero/negative balances: return `false` when `balance.amount <= 0`, and keep balances with `balancePriority > -99` (or whatever product rule is). Example: `return balancePriority > -99 && balance.amount > 0;` + +--- + +### 1.3 Missing `blockchain` on `WalletBalance` + +**Issue:** The code uses `balance.blockchain` but the interface `WalletBalance` only has `currency` and `amount`. TypeScript would error and runtime could be undefined. + +**Fix:** Add `blockchain` to the `WalletBalance` interface and use a union type instead of `any` for the `getPriority` argument (see type safety below). + +--- + +### 1.4 Unnecessary dependency in `useMemo` (`prices`) + +**Issue:** `sortedBalances` is memoized with dependency array `[balances, prices]`. The memoized value does not use `prices` at all. When `prices` changes, the filter/sort runs again for no reason. + +**Impact:** Extra work on every price update, more re-renders, worse performance. + +**Fix:** Remove `prices` from the dependency array: `[balances]`. + +--- + +### 1.5 Incomplete sort comparator (missing `return 0`) + +**Issue:** The sort callback returns `-1` or `1` but does not return `0` when `leftPriority === rightPriority`. In that case the function returns `undefined`, which can lead to unstable sort order and harder-to-reason-about behavior. + +**Fix:** Add `return 0` when priorities are equal: +`if (leftPriority > rightPriority) return -1; if (rightPriority > leftPriority) return 1; return 0;` + +--- + +### 1.6 Dead code and wrong data source for rows + +**Issue:** +- `formattedBalances` is computed (map that adds `formatted`) but never used. +- `rows` are built from `sortedBalances`, which is `WalletBalance[]` and has no `formatted` property. The code then uses `balance.formatted` in `WalletRow`, which would be undefined. + +**Impact:** Formatted amounts are wrong/undefined; unnecessary array allocation every render. + +**Fix:** Use `formattedBalances` when rendering rows, and derive rows from it. Optionally combine formatting with the same `useMemo` that produces sorted data to avoid two passes (see refactor). + +--- + +### 1.7 Using array index as `key` + +**Issue:** `key={index}` is used for list items. When the list is sorted/filtered, indices change and React can reuse the wrong component instance, leading to bugs and unnecessary DOM updates. + +**Fix:** Use a stable key, e.g. `key={balance.currency}` or `key={`${balance.currency}-${balance.blockchain}`}` if currency can repeat per chain. + +--- + +### 1.8 `getPriority` recreated every render + +**Issue:** `getPriority` is defined inside the component, so a new function is created on every render. It doesn’t depend on props/state. + +**Impact:** Minor CPU and memory churn; if passed to children or used in deps, can break memoization. + +**Fix:** Move `getPriority` outside the component (or to a utils file). It becomes a pure function and is not recreated. + +--- + +### 1.9 Type safety: `any` for blockchain + +**Issue:** `getPriority(blockchain: any)` violates the project rule “Avoid `any`” and loses type safety. + +**Fix:** Define a union type, e.g. +`type Blockchain = 'Osmosis' | 'Ethereum' | 'Arbitrum' | 'Zilliqa' | 'Neo';` +and use it in `WalletBalance.blockchain` and in `getPriority(blockchain: Blockchain)`. + +--- + +### 1.10 Empty/redundant `Props` interface + +**Issue:** `interface Props extends BoxProps {}` adds no members. It’s redundant. + +**Fix:** Use `BoxProps` directly as the component props type, or extend it only when you add specific props (e.g. `children`). + +--- + +### 1.11 Heavy logic inside the component + +**Issue:** Filtering, sorting, and formatting are done directly in the component. Rules say: “Business logic must not live in UI components” and “Shared logic goes to hooks”. + +**Impact:** Harder to test, reuse, and keep the component readable. + +**Fix:** Move “get filtered & sorted formatted balances” into a custom hook (e.g. `useSortedFormattedBalances(balances)`) or to a pure function in `utils`. The component only consumes the result and renders. + +--- + +### 1.12 No error handling + +**Issue:** `useWalletBalances()` and `usePrices()` are used with no loading/error handling. Rules: “Never ignore errors”, “Provide safe fallbacks”. + +**Fix:** Use error/loading states from the hooks and render loading UI or error boundaries and fallbacks (e.g. empty list, error message). + +--- + +### 1.13 Unused variable / missing definition + +**Issue:** `children` is destructured from `props` but never used. `classes.row` is used but `classes` is not defined in the snippet (likely from `makeStyles` or similar). + +**Fix:** Remove `children` if not needed, or use it in the JSX. Ensure `classes` is defined (e.g. from theme or CSS module) or replace with a string class name. + +--- + +### 1.14 Memoization of derived list for rows + +**Issue:** `formattedBalances` and `rows` are recomputed on every render. For larger lists this is unnecessary work when only `balances` or `prices` change. + +**Fix:** Use `useMemo` for the data that feeds the rows (e.g. formatted balances) with correct dependencies (`balances`, and `prices` only where needed for USD value). Optionally memoize row list or use `React.memo` on `WalletRow` for list performance. + +--- + +## 2. Summary Table + +| # | Category | Issue | Fix | +|---|-----------------|--------------------------------------------|-----| +| 1 | Bug | Wrong variable `lhsPriority` in filter | Use `balancePriority` | +| 2 | Bug | Filter keeps zero/negative balances | Keep positive (or clarify rule) | +| 3 | Type / data | `WalletBalance` missing `blockchain` | Add `blockchain` to interface | +| 4 | Performance | `prices` in useMemo deps unnecessarily | Remove `prices` from deps | +| 5 | Correctness | Sort comparator doesn’t return 0 | Return 0 when equal | +| 6 | Bug / dead code | `formattedBalances` unused; rows use wrong source | Use `formattedBalances` for rows | +| 7 | Anti-pattern | `key={index}` | Stable key (e.g. `currency`) | +| 8 | Performance | `getPriority` recreated every render | Move outside component | +| 9 | Type safety | `any` for blockchain | Use union type | +| 10| Code quality | Empty Props interface | Use BoxProps or extend meaningfully | +| 11| Architecture | Business logic in UI component | Move to hook or utils | +| 12| Robustness | No error/loading handling | Add error/loading states and fallbacks | +| 13| Clean code | Unused `children`; undefined `classes` | Remove or use; define classes | +| 14| Performance | No memo for formatted/row data | useMemo with correct deps | + +--- + +## 3. Refactored Code + +See the refactored implementation in: + +- `src/types/wallet.ts` — types and blockchain priority +- `src/utils/walletBalance.ts` — pure helpers (priority, filter, sort, format) +- `src/hooks/useSortedFormattedBalances.ts` — hook that encapsulates logic +- `src/components/WalletPage.tsx` — UI-only component + +This follows **rules.md**: separation of concerns, no `any`, reusable types, logic in hooks/utils, performance (memoization, stable keys), and room for error handling. diff --git a/src/problem3/src/components/WalletPage.tsx b/src/problem3/src/components/WalletPage.tsx new file mode 100644 index 0000000000..f29682133e --- /dev/null +++ b/src/problem3/src/components/WalletPage.tsx @@ -0,0 +1,51 @@ +import React, { useMemo } from 'react'; +import type { WalletPageProps, FormattedWalletBalance } from './types/wallet'; +import { useSortedFormattedBalances } from './hooks/useSortedFormattedBalances'; + +// Assume these hooks are provided elsewhere (e.g. from API layer) +declare function useWalletBalances(): import('./types/wallet').WalletBalance[]; +declare function usePrices(): Record; + +// Placeholder: replace with your actual WalletRow component +interface WalletRowProps { + className?: string; + key?: string; + amount: number; + usdValue: number; + formattedAmount: string; +} +function WalletRow({ className, amount, usdValue, formattedAmount }: WalletRowProps) { + return ( +
+ {formattedAmount} + ${usdValue.toFixed(2)} +
+ ); +} + +export const WalletPage: React.FC = (props) => { + const { ...rest } = props; + const balances = useWalletBalances(); + const prices = usePrices(); + + const formattedBalances = useSortedFormattedBalances(balances); + + const rows = useMemo( + () => + formattedBalances.map((balance: FormattedWalletBalance) => { + const usdValue = (prices[balance.currency] ?? 0) * balance.amount; + return ( + + ); + }), + [formattedBalances, prices] + ); + + return
{rows}
; +}; diff --git a/src/problem3/src/hooks/useSortedFormattedBalances.ts b/src/problem3/src/hooks/useSortedFormattedBalances.ts new file mode 100644 index 0000000000..a25d6797c4 --- /dev/null +++ b/src/problem3/src/hooks/useSortedFormattedBalances.ts @@ -0,0 +1,17 @@ +/** + * Hook: sorted and formatted wallet balances. + * Only depends on balances — not prices — to avoid unnecessary recalc (per rules.md §7). + */ + +import { useMemo } from 'react'; +import { getSortedFormattedBalances } from '../utils/walletBalance'; +import type { WalletBalance, FormattedWalletBalance } from '../types/wallet'; + +export function useSortedFormattedBalances( + balances: WalletBalance[] +): FormattedWalletBalance[] { + return useMemo( + () => getSortedFormattedBalances(balances), + [balances] + ); +} diff --git a/src/problem3/src/types/wallet.ts b/src/problem3/src/types/wallet.ts new file mode 100644 index 0000000000..3fd1bc1cc5 --- /dev/null +++ b/src/problem3/src/types/wallet.ts @@ -0,0 +1,31 @@ +/** + * Wallet and balance types. + * Reusable, strong typing — no `any` (per rules.md §9). + */ + +import type { ReactNode } from 'react'; + +export type Blockchain = + | 'Osmosis' + | 'Ethereum' + | 'Arbitrum' + | 'Zilliqa' + | 'Neo'; + +export interface WalletBalance { + currency: string; + amount: number; + blockchain: Blockchain; +} + +export interface FormattedWalletBalance extends WalletBalance { + formatted: string; +} + +export interface BoxProps { + className?: string; + children?: ReactNode; + [key: string]: unknown; +} + +export type WalletPageProps = BoxProps; diff --git a/src/problem3/src/utils/walletBalance.ts b/src/problem3/src/utils/walletBalance.ts new file mode 100644 index 0000000000..ba0c867d4f --- /dev/null +++ b/src/problem3/src/utils/walletBalance.ts @@ -0,0 +1,57 @@ +/** + * Pure wallet balance logic — testable, no UI (per rules.md: separation of concerns). + */ + +import type { Blockchain, WalletBalance, FormattedWalletBalance } from '../types/wallet'; + +const BLOCKCHAIN_PRIORITY: Record = { + Osmosis: 100, + Ethereum: 50, + Arbitrum: 30, + Zilliqa: 20, + Neo: 20, +}; + +const DEFAULT_PRIORITY = -99; + +export function getPriority(blockchain: Blockchain): number { + return BLOCKCHAIN_PRIORITY[blockchain] ?? DEFAULT_PRIORITY; +} + +/** + * Filter balances: known blockchain and positive amount. + * (Original kept zero/negative — likely bug; adjust rule if product needs otherwise.) + */ +export function filterBalances(balances: WalletBalance[]): WalletBalance[] { + return balances.filter((balance) => { + const priority = getPriority(balance.blockchain); + return priority > DEFAULT_PRIORITY && balance.amount > 0; + }); +} + +/** + * Sort by priority descending (higher first). + * Comparator returns -1 | 0 | 1 for stable sort. + */ +export function sortBalancesByPriority(balances: WalletBalance[]): WalletBalance[] { + return [...balances].sort((lhs, rhs) => { + const leftPriority = getPriority(lhs.blockchain); + const rightPriority = getPriority(rhs.blockchain); + if (leftPriority > rightPriority) return -1; + if (rightPriority > leftPriority) return 1; + return 0; + }); +} + +export function formatBalance(balance: WalletBalance): FormattedWalletBalance { + return { + ...balance, + formatted: balance.amount.toFixed(), + }; +} + +export function getSortedFormattedBalances(balances: WalletBalance[]): FormattedWalletBalance[] { + const filtered = filterBalances(balances); + const sorted = sortBalancesByPriority(filtered); + return sorted.map(formatBalance); +} From 48364c387390d5b88441740128c0f8676723853a Mon Sep 17 00:00:00 2001 From: ThangLCoders Date: Mon, 9 Mar 2026 18:46:01 +0700 Subject: [PATCH 4/4] chore: remove rules.md Made-with: Cursor --- rules.md | 294 ------------------------------------------------------- 1 file changed, 294 deletions(-) delete mode 100644 rules.md diff --git a/rules.md b/rules.md deleted file mode 100644 index ad14646228..0000000000 --- a/rules.md +++ /dev/null @@ -1,294 +0,0 @@ -# Senior Engineering Rules - -You are a Senior Software Engineer with 10+ years of experience. -All code generated must follow production-grade standards. - ---- - -# 1. Engineering Mindset - -Always think like a senior engineer working on a large-scale production system. - -Before writing code: - -1. Understand the problem clearly -2. Identify constraints -3. Consider edge cases -4. Design the architecture -5. Evaluate performance implications - -Never jump directly to code without reasoning. - ---- - -# 2. Code Quality Standards - -All code must be: - -- Clean -- Maintainable -- Scalable -- Readable -- Production-ready - -Follow these principles: - -- SOLID principles -- DRY (Don't Repeat Yourself) -- KISS (Keep It Simple) -- Separation of Concerns - -Avoid: - -- unnecessary abstractions -- duplicated logic -- deeply nested code -- unclear naming - ---- - -# 3. Naming Conventions - -Use clear and descriptive names. - -Bad: - -data -temp -x - -Good: - -userProfile -paymentRequest -gameSessionState - -Functions must describe behavior: - -calculateTotalBet() -fetchUserWallet() -validatePaymentRequest() - ---- - -# 4. File Structure - -Prefer scalable architecture. - -Example frontend structure: - -src - ├ components - ├ features - ├ hooks - ├ services - ├ store - ├ utils - └ types - -Rules: - -- Components must be small and reusable -- Business logic must not live in UI components -- API logic must be in services -- Shared logic goes to hooks - ---- - -# 5. React / Next.js Best Practices - -Follow modern React patterns. - -Prefer: - -- functional components -- hooks -- server components when possible -- minimal client state - -Avoid: - -- unnecessary re-renders -- heavy logic in components -- prop drilling (use context or state management) - ---- - -# 6. State Management - -Follow these rules: - -Server state: -Use TanStack Query - -Client state: -Use minimal state management - -Avoid global state unless necessary. - ---- - -# 7. Performance Rules - -Always consider performance. - -Optimize: - -- unnecessary renders -- large lists -- API calls -- heavy calculations - -Prefer: - -memoization -lazy loading -code splitting -virtualization for large lists - -Example tools: - -React.memo -useMemo -useCallback -dynamic imports - ---- - -# 8. Error Handling - -Never ignore errors. - -Handle: - -API errors -network failures -unexpected states - -Provide safe fallbacks. - -Example: - -try/catch -error boundaries -safe null checks - ---- - -# 9. Type Safety - -Prefer strong typing. - -Rules: - -- Avoid `any` -- Create reusable types -- Use generics when needed - -Example: - -type User = { - id: string - email: string - walletBalance: number -} - ---- - -# 10. API Design - -API interactions must be: - -- isolated -- typed -- reusable - -Example: - -services/ - paymentService.ts - userService.ts - -Avoid calling fetch/axios directly in components. - ---- - -# 11. Testing Mindset - -Always consider testability. - -Code must be easy to test. - -Functions should be: - -- pure when possible -- deterministic -- modular - -Include tests for: - -- normal cases -- edge cases -- invalid input - ---- - -# 12. Refactoring - -When modifying code: - -- preserve existing functionality -- reduce complexity -- improve readability -- remove dead code - -Never introduce breaking changes without reason. - ---- - -# 13. Code Review Mindset - -Before finishing: - -Check: - -- readability -- edge cases -- performance -- maintainability - -Ask: - -Would this scale to 100k users? -Would another engineer understand this easily? - ---- - -# 14. Algorithm Problems - -When solving coding challenges: - -1. Explain the approach -2. Discuss time complexity -3. Discuss space complexity -4. Provide optimized solution -5. Write clean code - ---- - -# 15. Security Awareness - -Avoid: - -- exposing sensitive data -- insecure API usage -- trusting client input - -Always validate inputs. - ---- - -# Final Rule - -Write code as if it will run in production with millions of users. -Prioritize clarity, maintainability, and performance. \ No newline at end of file