├── .eslintrc.json ├── .gitignore ├── README.md ├── components.json ├── eslint.config.js ├── eslint ├── eslint-ebn.json └── eslint-react.json ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── vite.svg ├── src ├── App.css ├── App.tsx ├── assets │ └── react.svg ├── components │ └── ui │ │ └── button.tsx ├── constants.ts ├── index.css ├── lib │ └── utils.ts ├── main.tsx ├── screens │ └── home │ │ └── index.tsx └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint/eslint-ebn", 4 | "eslint/eslint-react", 5 | "plugin:@typescript-eslint/eslint-recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:react-hooks/recommended", 8 | "react-app" 9 | ], 10 | "parser": "@typescript-eslint/parser", 11 | "plugins": [ 12 | "@typescript-eslint", 13 | "import", 14 | ], 15 | "settings": { 16 | "react": { 17 | "pragma": "React", 18 | "version": "18.2.0" 19 | } 20 | }, 21 | "env": { 22 | "jest": true 23 | }, 24 | "globals": { 25 | "__DEV__": true 26 | }, 27 | "rules": { 28 | "react/jsx-uses-react": "off", 29 | "react/react-in-jsx-scope": "off", 30 | "linebreak-style": 0, 31 | "eol-last": ["error", "always"], 32 | "global-require": 0, 33 | "no-undefined": 0, 34 | "no-shadow": "off", 35 | "react/display-name": [2, { "ignoreTranspilerName": false }], 36 | "react/jsx-filename-extension": 0, 37 | "react-hooks/exhaustive-deps": 0, 38 | "camelcase": [ 39 | 0, 40 | { 41 | "properties": "never" 42 | } 43 | ], 44 | "@typescript-eslint/ban-types": 0, 45 | "@typescript-eslint/no-non-null-assertion": 0, 46 | "@typescript-eslint/no-unused-vars": [ 47 | 2, 48 | { 49 | "vars": "all", 50 | "args": "after-used" 51 | } 52 | ], 53 | "@typescript-eslint/no-shadow": ["error"], 54 | "@typescript-eslint/no-explicit-any": "warn", 55 | "no-use-before-define": "off", 56 | "@typescript-eslint/no-use-before-define": 0, 57 | "@typescript-eslint/no-var-requires": 0, 58 | "@typescript-eslint/explicit-function-return-type": 0, 59 | "@typescript-eslint/explicit-module-boundary-types": "off", 60 | "no-underscore-dangle": "off", 61 | "indent": [2, 4, {"SwitchCase": 1}], 62 | "key-spacing": [2, { 63 | "singleLine": { 64 | "beforeColon": false, 65 | "afterColon": true 66 | }}], 67 | "@typescript-eslint/member-delimiter-style": 2, 68 | "@typescript-eslint/no-unsafe-declaration-merging": "off", 69 | "import/order": [ 70 | 2, 71 | { 72 | "groups": ["builtin", "external", "parent", "sibling", "index", "type"], 73 | "newlines-between": "always", 74 | "pathGroups": [ 75 | { 76 | "pattern": "{@(@/action|@/app|@/assets|@/client|@/components|@/constants|@/database|@/global|@/hooks|@/screens|@/storage|@/store|@/typings|@/utils)/**,@(@/constants|@/notifications|@/store|@/websocket)}", 77 | "group": "external", 78 | "position": "after" 79 | }, 80 | { 81 | "pattern": "src/**", 82 | "group": "parent", 83 | "position": "before" 84 | } 85 | ], 86 | "alphabetize": { 87 | "order": "asc", 88 | "caseInsensitive": true 89 | }, 90 | "pathGroupsExcludedImportTypes": ["type"] 91 | } 92 | ] 93 | }, 94 | "overrides": [ 95 | { 96 | "files": ["*.test.js", "*.test.jsx"], 97 | "env": { 98 | "jest": true 99 | } 100 | }, 101 | { 102 | "files": ["detox/e2e/**"], 103 | "globals": { 104 | "by": true, 105 | "detox": true, 106 | "device": true, 107 | "element": true, 108 | "waitFor": true 109 | }, 110 | "rules": { 111 | "func-names": 0, 112 | "import/no-unresolved": 0, 113 | "max-nested-callbacks": 0, 114 | "no-process-env": 0, 115 | "no-unused-expressions": 0 116 | } 117 | } 118 | ] 119 | } 120 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default tseslint.config({ 18 | languageOptions: { 19 | // other options... 20 | parserOptions: { 21 | project: ['./tsconfig.node.json', './tsconfig.app.json'], 22 | tsconfigRootDir: import.meta.dirname, 23 | }, 24 | }, 25 | }) 26 | ``` 27 | 28 | - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` 29 | - Optionally add `...tseslint.configs.stylisticTypeChecked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: 31 | 32 | ```js 33 | // eslint.config.js 34 | import react from 'eslint-plugin-react' 35 | 36 | export default tseslint.config({ 37 | // Set the react version 38 | settings: { react: { version: '18.3' } }, 39 | plugins: { 40 | // Add the react plugin 41 | react, 42 | }, 43 | rules: { 44 | // other rules... 45 | // Enable its recommended rules 46 | ...react.configs.recommended.rules, 47 | ...react.configs['jsx-runtime'].rules, 48 | }, 49 | }) 50 | ``` 51 | # calc-fe 52 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config({ 8 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 9 | files: ['**/*.{ts,tsx}'], 10 | ignores: ['dist'], 11 | languageOptions: { 12 | ecmaVersion: 2020, 13 | globals: globals.browser, 14 | }, 15 | plugins: { 16 | 'react-hooks': reactHooks, 17 | 'react-refresh': reactRefresh, 18 | }, 19 | rules: { 20 | ...reactHooks.configs.recommended.rules, 21 | 'react-refresh/only-export-components': [ 22 | 'warn', 23 | { allowConstantExport: true }, 24 | ], 25 | }, 26 | }) 27 | -------------------------------------------------------------------------------- /eslint/eslint-ebn.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended" 4 | ], 5 | "parserOptions": { 6 | "ecmaVersion": 8, 7 | "sourceType": "module", 8 | "ecmaFeatures": { 9 | "jsx": true, 10 | "impliedStrict": true, 11 | "modules": true 12 | } 13 | }, 14 | "parser": "babel-eslint", 15 | "plugins": [ 16 | "header" 17 | ], 18 | "env": { 19 | "browser": true, 20 | "node": true, 21 | "jquery": true, 22 | "es6": true 23 | }, 24 | "rules": { 25 | "array-bracket-spacing": [ 26 | 2, 27 | "never" 28 | ], 29 | "array-callback-return": 2, 30 | "arrow-body-style": 0, 31 | "arrow-parens": [ 32 | 2, 33 | "always" 34 | ], 35 | "arrow-spacing": [ 36 | 2, 37 | { 38 | "before": true, 39 | "after": true 40 | } 41 | ], 42 | "block-scoped-var": 2, 43 | "brace-style": [ 44 | 2, 45 | "1tbs", 46 | { 47 | "allowSingleLine": false 48 | } 49 | ], 50 | "camelcase": [ 51 | 2, 52 | { 53 | "properties": "never" 54 | } 55 | ], 56 | "capitalized-comments": 0, 57 | "class-methods-use-this": 0, 58 | "comma-dangle": [ 59 | 2, 60 | "always-multiline" 61 | ], 62 | "comma-spacing": [ 63 | 2, 64 | { 65 | "before": false, 66 | "after": true 67 | } 68 | ], 69 | "comma-style": [ 70 | 2, 71 | "last" 72 | ], 73 | "complexity": [ 74 | 0, 75 | 10 76 | ], 77 | "computed-property-spacing": [ 78 | 2, 79 | "never" 80 | ], 81 | "consistent-return": 2, 82 | "consistent-this": [ 83 | 2, 84 | "self" 85 | ], 86 | "constructor-super": 2, 87 | "curly": [ 88 | 2, 89 | "all" 90 | ], 91 | "dot-location": [ 92 | 2, 93 | "object" 94 | ], 95 | "dot-notation": 2, 96 | "eqeqeq": [ 97 | 2, 98 | "smart" 99 | ], 100 | "func-call-spacing": [ 101 | 2, 102 | "never" 103 | ], 104 | "func-name-matching": 0, 105 | "func-names": 2, 106 | "func-style": [ 107 | 2, 108 | "declaration", 109 | { 110 | "allowArrowFunctions": true 111 | } 112 | ], 113 | "generator-star-spacing": [ 114 | 2, 115 | { 116 | "before": false, 117 | "after": true 118 | } 119 | ], 120 | "global-require": 2, 121 | "guard-for-in": 2, 122 | "header/header": [ 123 | 2, 124 | "line", 125 | " Copyright (c) 2024-present Error By Night, Inc. All Rights Reserved." 126 | ], 127 | "id-blacklist": 0, 128 | "indent": [ 129 | 2, 130 | 4, 131 | { 132 | "SwitchCase": 0 133 | } 134 | ], 135 | "jsx-quotes": [ 136 | 2, 137 | "prefer-single" 138 | ], 139 | "key-spacing": [ 140 | 2, 141 | { 142 | "beforeColon": false, 143 | "afterColon": true, 144 | "mode": "strict" 145 | } 146 | ], 147 | "keyword-spacing": [ 148 | 2, 149 | { 150 | "before": true, 151 | "after": true, 152 | "overrides": {} 153 | } 154 | ], 155 | "line-comment-position": 0, 156 | "linebreak-style": 2, 157 | "lines-around-comment": [ 158 | 2, 159 | { 160 | "beforeBlockComment": true, 161 | "beforeLineComment": true, 162 | "allowBlockStart": true, 163 | "allowBlockEnd": true 164 | } 165 | ], 166 | "max-lines": [ 167 | 1, 168 | { 169 | "max": 550, 170 | "skipBlankLines": true, 171 | "skipComments": true 172 | } 173 | ], 174 | "max-nested-callbacks": [ 175 | 2, 176 | { 177 | "max": 2 178 | } 179 | ], 180 | "max-statements-per-line": [ 181 | 2, 182 | { 183 | "max": 1 184 | } 185 | ], 186 | "multiline-ternary": [ 187 | 1, 188 | "never" 189 | ], 190 | "new-cap": 2, 191 | "new-parens": 2, 192 | "newline-before-return": 0, 193 | "newline-per-chained-call": 0, 194 | "no-alert": 2, 195 | "no-array-constructor": 2, 196 | "no-await-in-loop": 2, 197 | "no-caller": 2, 198 | "no-case-declarations": 2, 199 | "no-class-assign": 2, 200 | "no-compare-neg-zero": 2, 201 | "no-cond-assign": [ 202 | 2, 203 | "except-parens" 204 | ], 205 | "no-confusing-arrow": 2, 206 | "no-console": 2, 207 | "no-const-assign": 2, 208 | "no-constant-condition": 2, 209 | "no-debugger": 2, 210 | "no-div-regex": 2, 211 | "no-dupe-args": 2, 212 | "no-dupe-class-members": 2, 213 | "no-dupe-keys": 2, 214 | "no-duplicate-case": 2, 215 | "no-duplicate-imports": [ 216 | 2, 217 | { 218 | "includeExports": true 219 | } 220 | ], 221 | "no-else-return": 2, 222 | "no-empty": 2, 223 | "no-empty-function": 2, 224 | "no-empty-pattern": 2, 225 | "no-eval": 2, 226 | "no-ex-assign": 2, 227 | "no-extend-native": 2, 228 | "no-extra-bind": 2, 229 | "no-extra-label": 2, 230 | "no-extra-parens": 0, 231 | "no-extra-semi": 2, 232 | "no-fallthrough": 2, 233 | "no-floating-decimal": 2, 234 | "no-func-assign": 2, 235 | "no-global-assign": 2, 236 | "no-implicit-coercion": 2, 237 | "no-implicit-globals": 0, 238 | "no-implied-eval": 2, 239 | "no-inner-declarations": 0, 240 | "no-invalid-regexp": 2, 241 | "no-irregular-whitespace": 2, 242 | "no-iterator": 2, 243 | "no-labels": 2, 244 | "no-lone-blocks": 2, 245 | "no-lonely-if": 2, 246 | "no-loop-func": 2, 247 | "no-magic-numbers": 0, 248 | "no-mixed-operators": [ 249 | 2, 250 | { 251 | "allowSamePrecedence": false 252 | } 253 | ], 254 | "no-mixed-spaces-and-tabs": 2, 255 | "no-multi-assign": 2, 256 | "no-multi-spaces": [ 257 | 2, 258 | { 259 | "exceptions": { 260 | "Property": false 261 | } 262 | } 263 | ], 264 | "no-multi-str": 0, 265 | "no-multiple-empty-lines": [ 266 | 2, 267 | { 268 | "max": 1 269 | } 270 | ], 271 | "no-native-reassign": 2, 272 | "no-negated-condition": 2, 273 | "no-nested-ternary": 2, 274 | "no-new": 2, 275 | "no-new-func": 2, 276 | "no-new-object": 2, 277 | "no-new-symbol": 2, 278 | "no-new-wrappers": 2, 279 | "no-octal-escape": 2, 280 | "no-param-reassign": 2, 281 | "no-process-env": 2, 282 | "no-process-exit": 2, 283 | "no-proto": 2, 284 | "no-prototype-builtins": 1, 285 | "no-redeclare": 2, 286 | "no-return-assign": [ 287 | 2, 288 | "always" 289 | ], 290 | "no-return-await": 2, 291 | "no-script-url": 2, 292 | "no-self-assign": [ 293 | 2, 294 | { 295 | "props": true 296 | } 297 | ], 298 | "no-self-compare": 2, 299 | "no-sequences": 2, 300 | "no-shadow": [ 301 | 2, 302 | { 303 | "hoist": "functions" 304 | } 305 | ], 306 | "no-shadow-restricted-names": 2, 307 | "no-spaced-func": 2, 308 | "no-tabs": 0, 309 | "no-template-curly-in-string": 2, 310 | "no-ternary": 0, 311 | "no-this-before-super": 2, 312 | "no-throw-literal": 2, 313 | "no-trailing-spaces": [ 314 | 2, 315 | { 316 | "skipBlankLines": false 317 | } 318 | ], 319 | "no-undef-init": 2, 320 | "no-undefined": 2, 321 | "no-underscore-dangle": 2, 322 | "no-unexpected-multiline": 2, 323 | "no-unmodified-loop-condition": 2, 324 | "no-unneeded-ternary": [ 325 | 2, 326 | { 327 | "defaultAssignment": false 328 | } 329 | ], 330 | "no-unreachable": 2, 331 | "no-unsafe-finally": 2, 332 | "no-unsafe-negation": 2, 333 | "no-unused-expressions": 2, 334 | "no-unused-vars": [ 335 | 2, 336 | { 337 | "vars": "all", 338 | "args": "after-used" 339 | } 340 | ], 341 | "no-use-before-define": [ 342 | 2, 343 | { 344 | "classes": false, 345 | "functions": false, 346 | "variables": false 347 | } 348 | ], 349 | "no-useless-computed-key": 2, 350 | "no-useless-concat": 2, 351 | "no-useless-constructor": 2, 352 | "no-useless-escape": 2, 353 | "no-useless-rename": 2, 354 | "no-useless-return": 2, 355 | "no-var": 0, 356 | "no-void": 2, 357 | "no-warning-comments": 1, 358 | "no-whitespace-before-property": 2, 359 | "no-with": 2, 360 | "object-curly-newline": 0, 361 | "object-curly-spacing": [ 362 | 2, 363 | "never" 364 | ], 365 | "object-property-newline": [ 366 | 2, 367 | { 368 | "allowMultiplePropertiesPerLine": true 369 | } 370 | ], 371 | "object-shorthand": [ 372 | 2, 373 | "always" 374 | ], 375 | "one-var": [ 376 | 2, 377 | "never" 378 | ], 379 | "one-var-declaration-per-line": 0, 380 | "operator-assignment": [ 381 | 2, 382 | "always" 383 | ], 384 | "operator-linebreak": [ 385 | 2, 386 | "after" 387 | ], 388 | "padded-blocks": [ 389 | 2, 390 | "never" 391 | ], 392 | "prefer-arrow-callback": 2, 393 | "prefer-const": 2, 394 | "prefer-destructuring": 0, 395 | "prefer-numeric-literals": 2, 396 | "prefer-promise-reject-errors": 2, 397 | "prefer-rest-params": 2, 398 | "prefer-spread": 2, 399 | "prefer-template": 0, 400 | "quote-props": [ 401 | 2, 402 | "as-needed" 403 | ], 404 | "quotes": [ 405 | 2, 406 | "single", 407 | "avoid-escape" 408 | ], 409 | "radix": 2, 410 | "require-yield": 2, 411 | "rest-spread-spacing": [ 412 | 2, 413 | "never" 414 | ], 415 | "semi": [ 416 | 2, 417 | "always" 418 | ], 419 | "semi-spacing": [ 420 | 2, 421 | { 422 | "before": false, 423 | "after": true 424 | } 425 | ], 426 | "sort-imports": 0, 427 | "sort-keys": 0, 428 | "space-before-blocks": [ 429 | 2, 430 | "always" 431 | ], 432 | "space-before-function-paren": [ 433 | 2, 434 | { 435 | "anonymous": "never", 436 | "named": "never", 437 | "asyncArrow": "always" 438 | } 439 | ], 440 | "space-in-parens": [ 441 | 2, 442 | "never" 443 | ], 444 | "space-infix-ops": 2, 445 | "space-unary-ops": [ 446 | 2, 447 | { 448 | "words": true, 449 | "nonwords": false 450 | } 451 | ], 452 | "symbol-description": 2, 453 | "template-curly-spacing": [ 454 | 2, 455 | "never" 456 | ], 457 | "valid-typeof": [ 458 | 2, 459 | { 460 | "requireStringLiterals": false 461 | } 462 | ], 463 | "vars-on-top": 0, 464 | "wrap-iife": [ 465 | 2, 466 | "outside" 467 | ], 468 | "wrap-regex": 2, 469 | "yoda": [ 470 | 2, 471 | "never", 472 | { 473 | "exceptRange": false, 474 | "onlyEquality": false 475 | } 476 | ], 477 | "@typescript-eslint/array-type": [2, {"default": "array-simple"}], 478 | "@typescript-eslint/member-delimiter-style": 2, 479 | "@typescript-eslint/type-annotation-spacing": 2 480 | }, 481 | "overrides": [ 482 | { 483 | "files": ["*.test.js", "*.test.jsx", "*.test.ts", "*.test.tsx", "tests/**"], 484 | "globals": { 485 | "after": true, 486 | "afterAll": true, 487 | "afterEach": true, 488 | "before": true, 489 | "beforeAll": true, 490 | "beforeEach": true, 491 | "describe": true, 492 | "expect": true, 493 | "it": true, 494 | "jest": true, 495 | "test": true 496 | }, 497 | "rules": { 498 | "no-empty-function": 0, 499 | "no-console": 0, 500 | "max-nested-callbacks": 0, 501 | "no-undefined": 0 502 | } 503 | } 504 | ] 505 | } 506 | -------------------------------------------------------------------------------- /eslint/eslint-react.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:react/recommended" 4 | ], 5 | "plugins": [ 6 | "react" 7 | ], 8 | "rules": { 9 | "react/display-name": [ 10 | 0, 11 | { 12 | "ignoreTranspilerName": false 13 | } 14 | ], 15 | "react/forbid-component-props": 0, 16 | "react/forbid-elements": [ 17 | 2, 18 | { 19 | "forbid": [ 20 | "embed" 21 | ] 22 | } 23 | ], 24 | "react/jsx-boolean-value": [ 25 | 2, 26 | "always" 27 | ], 28 | "react/jsx-closing-bracket-location": [ 29 | 2, 30 | { 31 | "location": "tag-aligned" 32 | } 33 | ], 34 | "react/jsx-curly-spacing": [ 35 | 2, 36 | "never" 37 | ], 38 | "react/jsx-equals-spacing": [ 39 | 2, 40 | "never" 41 | ], 42 | "react/jsx-filename-extension": 2, 43 | "react/jsx-first-prop-new-line": [ 44 | 2, 45 | "multiline" 46 | ], 47 | "react/jsx-handler-names": 0, 48 | "react/jsx-indent": [ 49 | 2, 50 | 4 51 | ], 52 | "react/jsx-indent-props": [ 53 | 2, 54 | 4 55 | ], 56 | "react/jsx-key": 2, 57 | "react/jsx-max-props-per-line": [ 58 | 2, 59 | { 60 | "maximum": 1 61 | } 62 | ], 63 | "react/jsx-no-bind": 0, 64 | "react/jsx-no-comment-textnodes": 2, 65 | "react/jsx-no-duplicate-props": [ 66 | 2, 67 | { 68 | "ignoreCase": false 69 | } 70 | ], 71 | "react/jsx-no-target-blank": 2, 72 | "react/jsx-no-undef": 2, 73 | "react/jsx-pascal-case": 2, 74 | "react/jsx-tag-spacing": [ 75 | 2, 76 | { 77 | "closingSlash": "never", 78 | "beforeSelfClosing": "never", 79 | "afterOpening": "never" 80 | } 81 | ], 82 | "react/jsx-uses-react": 2, 83 | "react/jsx-uses-vars": 2, 84 | "react/jsx-wrap-multilines": 2, 85 | "react/no-array-index-key": 1, 86 | "react/no-children-prop": 2, 87 | "react/no-danger": 0, 88 | "react/no-danger-with-children": 2, 89 | "react/no-deprecated": 1, 90 | "react/no-did-mount-set-state": 2, 91 | "react/no-did-update-set-state": 2, 92 | "react/no-direct-mutation-state": 2, 93 | "react/no-find-dom-node": 1, 94 | "react/no-is-mounted": 2, 95 | "react/no-multi-comp": [ 96 | 2, 97 | { 98 | "ignoreStateless": true 99 | } 100 | ], 101 | "react/no-render-return-value": 2, 102 | "react/no-set-state": 0, 103 | "react/no-string-refs": 0, 104 | "react/no-unescaped-entities": 2, 105 | "react/no-unknown-property": 2, 106 | "react/no-unused-prop-types": [ 107 | 1, 108 | { 109 | "skipShapeProps": true 110 | } 111 | ], 112 | "react/prefer-es6-class": 2, 113 | "react/prefer-stateless-function": 0, 114 | "react/require-default-props": 0, 115 | "react/require-optimization": 1, 116 | "react/require-render-return": 2, 117 | "react/self-closing-comp": 2, 118 | "react/sort-comp": 0, 119 | "react/style-prop-object": 2, 120 | "react/prop-types": [0] 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-project", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "npm run lint & vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@mantine/core": "^7.12.0", 14 | "@mantine/hooks": "^7.12.0", 15 | "@radix-ui/react-slot": "^1.1.0", 16 | "@typescript-eslint/eslint-plugin": "^8.0.1", 17 | "axios": "^1.7.3", 18 | "class-variance-authority": "^0.7.0", 19 | "clsx": "^2.1.1", 20 | "html2canvas": "^1.4.1", 21 | "lazy-brush": "^2.0.1", 22 | "lucide-react": "^0.426.0", 23 | "mathjax": "^3.2.2", 24 | "react": "^18.3.1", 25 | "react-dom": "^18.3.1", 26 | "react-draggable": "^4.4.6", 27 | "react-router-dom": "^6.26.0", 28 | "tailwind-merge": "^2.4.0", 29 | "tailwindcss-animate": "^1.0.7" 30 | }, 31 | "devDependencies": { 32 | "@eslint/js": "^9.8.0", 33 | "@types/mathjax": "^0.0.40", 34 | "@types/node": "^22.1.0", 35 | "@types/react": "^18.3.3", 36 | "@types/react-dom": "^18.3.0", 37 | "@vitejs/plugin-react": "^4.3.1", 38 | "autoprefixer": "^10.4.20", 39 | "eslint": "^9.9.1", 40 | "eslint-plugin-react-hooks": "^5.1.0-rc.0", 41 | "eslint-plugin-react-refresh": "^0.4.9", 42 | "globals": "^15.9.0", 43 | "postcss": "^8.4.41", 44 | "tailwindcss": "^3.4.9", 45 | "typescript": "^5.5.3", 46 | "typescript-eslint": "^8.0.0", 47 | "vite": "^5.4.0", 48 | "vite-plugin-eslint": "^1.8.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import {createBrowserRouter, RouterProvider} from 'react-router-dom'; 2 | import '@mantine/core/styles.css'; 3 | import { MantineProvider } from '@mantine/core'; 4 | 5 | import Home from '@/screens/home'; 6 | 7 | import '@/index.css'; 8 | 9 | const paths = [ 10 | { 11 | path: '/', 12 | element: ( 13 | 14 | ), 15 | }, 16 | ]; 17 | 18 | const BrowserRouter = createBrowserRouter(paths); 19 | 20 | const App = () => { 21 | return ( 22 | 23 | 24 | 25 | ) 26 | }; 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | const SWATCHES = [ 2 | "#000000", // black 3 | "#ffffff", // white 4 | "#ee3333", // red 5 | "#e64980", // pink 6 | "#be4bdb", // purple 7 | "#893200", // brown 8 | "#228be6", // blue 9 | "#3333ee", // dark blue 10 | "#40c057", // green 11 | "#00aa00", // dark green 12 | "#fab005", // yellow 13 | "#fd7e14", // orange 14 | ]; 15 | 16 | export { SWATCHES }; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 222.2 84% 4.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 222.2 84% 4.9%; 13 | --primary: 222.2 47.4% 11.2%; 14 | --primary-foreground: 210 40% 98%; 15 | --secondary: 210 40% 96.1%; 16 | --secondary-foreground: 222.2 47.4% 11.2%; 17 | --muted: 210 40% 96.1%; 18 | --muted-foreground: 215.4 16.3% 46.9%; 19 | --accent: 210 40% 96.1%; 20 | --accent-foreground: 222.2 47.4% 11.2%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 210 40% 98%; 23 | --border: 214.3 31.8% 91.4%; 24 | --input: 214.3 31.8% 91.4%; 25 | --ring: 222.2 84% 4.9%; 26 | --radius: 0.5rem; 27 | --chart-1: 12 76% 61%; 28 | --chart-2: 173 58% 39%; 29 | --chart-3: 197 37% 24%; 30 | --chart-4: 43 74% 66%; 31 | --chart-5: 27 87% 67%; 32 | } 33 | 34 | .dark { 35 | --background: 222.2 84% 4.9%; 36 | --foreground: 210 40% 98%; 37 | --card: 222.2 84% 4.9%; 38 | --card-foreground: 210 40% 98%; 39 | --popover: 222.2 84% 4.9%; 40 | --popover-foreground: 210 40% 98%; 41 | --primary: 210 40% 98%; 42 | --primary-foreground: 222.2 47.4% 11.2%; 43 | --secondary: 217.2 32.6% 17.5%; 44 | --secondary-foreground: 210 40% 98%; 45 | --muted: 217.2 32.6% 17.5%; 46 | --muted-foreground: 215 20.2% 65.1%; 47 | --accent: 217.2 32.6% 17.5%; 48 | --accent-foreground: 210 40% 98%; 49 | --destructive: 0 62.8% 30.6%; 50 | --destructive-foreground: 210 40% 98%; 51 | --border: 217.2 32.6% 17.5%; 52 | --input: 217.2 32.6% 17.5%; 53 | --ring: 212.7 26.8% 83.9%; 54 | --chart-1: 220 70% 50%; 55 | --chart-2: 160 60% 45%; 56 | --chart-3: 30 80% 55%; 57 | --chart-4: 280 65% 60%; 58 | --chart-5: 340 75% 55%; 59 | } 60 | } 61 | 62 | @layer base { 63 | * { 64 | @apply border-border; 65 | } 66 | body { 67 | @apply bg-background text-foreground; 68 | } 69 | } -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | 6 | createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /src/screens/home/index.tsx: -------------------------------------------------------------------------------- 1 | import { ColorSwatch, Group } from '@mantine/core'; 2 | import { Button } from '@/components/ui/button'; 3 | import { useEffect, useRef, useState } from 'react'; 4 | import axios from 'axios'; 5 | import Draggable from 'react-draggable'; 6 | import {SWATCHES} from '@/constants'; 7 | // import {LazyBrush} from 'lazy-brush'; 8 | 9 | interface GeneratedResult { 10 | expression: string; 11 | answer: string; 12 | } 13 | 14 | interface Response { 15 | expr: string; 16 | result: string; 17 | assign: boolean; 18 | } 19 | 20 | export default function Home() { 21 | const canvasRef = useRef(null); 22 | const [isDrawing, setIsDrawing] = useState(false); 23 | const [color, setColor] = useState('rgb(255, 255, 255)'); 24 | const [reset, setReset] = useState(false); 25 | const [dictOfVars, setDictOfVars] = useState({}); 26 | const [result, setResult] = useState(); 27 | const [latexPosition, setLatexPosition] = useState({ x: 10, y: 200 }); 28 | const [latexExpression, setLatexExpression] = useState>([]); 29 | 30 | // const lazyBrush = new LazyBrush({ 31 | // radius: 10, 32 | // enabled: true, 33 | // initialPoint: { x: 0, y: 0 }, 34 | // }); 35 | 36 | useEffect(() => { 37 | if (latexExpression.length > 0 && window.MathJax) { 38 | setTimeout(() => { 39 | window.MathJax.Hub.Queue(["Typeset", window.MathJax.Hub]); 40 | }, 0); 41 | } 42 | }, [latexExpression]); 43 | 44 | useEffect(() => { 45 | if (result) { 46 | renderLatexToCanvas(result.expression, result.answer); 47 | } 48 | }, [result]); 49 | 50 | useEffect(() => { 51 | if (reset) { 52 | resetCanvas(); 53 | setLatexExpression([]); 54 | setResult(undefined); 55 | setDictOfVars({}); 56 | setReset(false); 57 | } 58 | }, [reset]); 59 | 60 | useEffect(() => { 61 | const canvas = canvasRef.current; 62 | 63 | if (canvas) { 64 | const ctx = canvas.getContext('2d'); 65 | if (ctx) { 66 | canvas.width = window.innerWidth; 67 | canvas.height = window.innerHeight - canvas.offsetTop; 68 | ctx.lineCap = 'round'; 69 | ctx.lineWidth = 3; 70 | } 71 | 72 | } 73 | const script = document.createElement('script'); 74 | script.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-MML-AM_CHTML'; 75 | script.async = true; 76 | document.head.appendChild(script); 77 | 78 | script.onload = () => { 79 | window.MathJax.Hub.Config({ 80 | tex2jax: {inlineMath: [['$', '$'], ['\\(', '\\)']]}, 81 | }); 82 | }; 83 | 84 | return () => { 85 | document.head.removeChild(script); 86 | }; 87 | 88 | }, []); 89 | 90 | const renderLatexToCanvas = (expression: string, answer: string) => { 91 | const latex = `\\(\\LARGE{${expression} = ${answer}}\\)`; 92 | setLatexExpression([...latexExpression, latex]); 93 | 94 | // Clear the main canvas 95 | const canvas = canvasRef.current; 96 | if (canvas) { 97 | const ctx = canvas.getContext('2d'); 98 | if (ctx) { 99 | ctx.clearRect(0, 0, canvas.width, canvas.height); 100 | } 101 | } 102 | }; 103 | 104 | 105 | const resetCanvas = () => { 106 | const canvas = canvasRef.current; 107 | if (canvas) { 108 | const ctx = canvas.getContext('2d'); 109 | if (ctx) { 110 | ctx.clearRect(0, 0, canvas.width, canvas.height); 111 | } 112 | } 113 | }; 114 | 115 | const startDrawing = (e: React.MouseEvent) => { 116 | const canvas = canvasRef.current; 117 | if (canvas) { 118 | canvas.style.background = 'black'; 119 | const ctx = canvas.getContext('2d'); 120 | if (ctx) { 121 | ctx.beginPath(); 122 | ctx.moveTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY); 123 | setIsDrawing(true); 124 | } 125 | } 126 | }; 127 | const draw = (e: React.MouseEvent) => { 128 | if (!isDrawing) { 129 | return; 130 | } 131 | const canvas = canvasRef.current; 132 | if (canvas) { 133 | const ctx = canvas.getContext('2d'); 134 | if (ctx) { 135 | ctx.strokeStyle = color; 136 | ctx.lineTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY); 137 | ctx.stroke(); 138 | } 139 | } 140 | }; 141 | const stopDrawing = () => { 142 | setIsDrawing(false); 143 | }; 144 | 145 | const runRoute = async () => { 146 | const canvas = canvasRef.current; 147 | 148 | if (canvas) { 149 | const response = await axios({ 150 | method: 'post', 151 | url: `${import.meta.env.VITE_API_URL}/calculate`, 152 | data: { 153 | image: canvas.toDataURL('image/png'), 154 | dict_of_vars: dictOfVars 155 | } 156 | }); 157 | 158 | const resp = await response.data; 159 | console.log('Response', resp); 160 | resp.data.forEach((data: Response) => { 161 | if (data.assign === true) { 162 | // dict_of_vars[resp.result] = resp.answer; 163 | setDictOfVars({ 164 | ...dictOfVars, 165 | [data.expr]: data.result 166 | }); 167 | } 168 | }); 169 | const ctx = canvas.getContext('2d'); 170 | const imageData = ctx!.getImageData(0, 0, canvas.width, canvas.height); 171 | let minX = canvas.width, minY = canvas.height, maxX = 0, maxY = 0; 172 | 173 | for (let y = 0; y < canvas.height; y++) { 174 | for (let x = 0; x < canvas.width; x++) { 175 | const i = (y * canvas.width + x) * 4; 176 | if (imageData.data[i + 3] > 0) { // If pixel is not transparent 177 | minX = Math.min(minX, x); 178 | minY = Math.min(minY, y); 179 | maxX = Math.max(maxX, x); 180 | maxY = Math.max(maxY, y); 181 | } 182 | } 183 | } 184 | 185 | const centerX = (minX + maxX) / 2; 186 | const centerY = (minY + maxY) / 2; 187 | 188 | setLatexPosition({ x: centerX, y: centerY }); 189 | resp.data.forEach((data: Response) => { 190 | setTimeout(() => { 191 | setResult({ 192 | expression: data.expr, 193 | answer: data.result 194 | }); 195 | }, 1000); 196 | }); 197 | } 198 | }; 199 | 200 | return ( 201 | <> 202 |
203 | 211 | 212 | {SWATCHES.map((swatch) => ( 213 | setColor(swatch)} /> 214 | ))} 215 | 216 | 224 |
225 | 234 | 235 | {latexExpression && latexExpression.map((latex, index) => ( 236 | setLatexPosition({ x: data.x, y: data.y })} 240 | > 241 |
242 |
{latex}
243 |
244 |
245 | ))} 246 | 247 | ); 248 | } 249 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | './pages/**/*.{ts,tsx}', 6 | './components/**/*.{ts,tsx}', 7 | './app/**/*.{ts,tsx}', 8 | './src/**/*.{ts,tsx}', 9 | ], 10 | prefix: "", 11 | theme: { 12 | container: { 13 | center: true, 14 | padding: "2rem", 15 | screens: { 16 | "2xl": "1400px", 17 | }, 18 | }, 19 | extend: { 20 | colors: { 21 | border: "hsl(var(--border))", 22 | input: "hsl(var(--input))", 23 | ring: "hsl(var(--ring))", 24 | background: "hsl(var(--background))", 25 | foreground: "hsl(var(--foreground))", 26 | primary: { 27 | DEFAULT: "hsl(var(--primary))", 28 | foreground: "hsl(var(--primary-foreground))", 29 | }, 30 | secondary: { 31 | DEFAULT: "hsl(var(--secondary))", 32 | foreground: "hsl(var(--secondary-foreground))", 33 | }, 34 | destructive: { 35 | DEFAULT: "hsl(var(--destructive))", 36 | foreground: "hsl(var(--destructive-foreground))", 37 | }, 38 | muted: { 39 | DEFAULT: "hsl(var(--muted))", 40 | foreground: "hsl(var(--muted-foreground))", 41 | }, 42 | accent: { 43 | DEFAULT: "hsl(var(--accent))", 44 | foreground: "hsl(var(--accent-foreground))", 45 | }, 46 | popover: { 47 | DEFAULT: "hsl(var(--popover))", 48 | foreground: "hsl(var(--popover-foreground))", 49 | }, 50 | card: { 51 | DEFAULT: "hsl(var(--card))", 52 | foreground: "hsl(var(--card-foreground))", 53 | }, 54 | }, 55 | borderRadius: { 56 | lg: "var(--radius)", 57 | md: "calc(var(--radius) - 2px)", 58 | sm: "calc(var(--radius) - 4px)", 59 | }, 60 | keyframes: { 61 | "accordion-down": { 62 | from: { height: "0" }, 63 | to: { height: "var(--radix-accordion-content-height)" }, 64 | }, 65 | "accordion-up": { 66 | from: { height: "var(--radix-accordion-content-height)" }, 67 | to: { height: "0" }, 68 | }, 69 | }, 70 | animation: { 71 | "accordion-down": "accordion-down 0.2s ease-out", 72 | "accordion-up": "accordion-up 0.2s ease-out", 73 | }, 74 | }, 75 | }, 76 | plugins: [require("tailwindcss-animate")], 77 | } -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | "baseUrl": ".", 9 | "paths": { 10 | "@/*": ["./src/*"] 11 | }, 12 | /* Bundler mode */ 13 | "moduleResolution": "bundler", 14 | "allowImportingTsExtensions": true, 15 | "isolatedModules": true, 16 | "moduleDetection": "force", 17 | "noEmit": true, 18 | "jsx": "react-jsx", 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true 25 | }, 26 | "include": ["src"] 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | }, 8 | "files": [], 9 | "references": [ 10 | { "path": "./tsconfig.app.json" }, 11 | { "path": "./tsconfig.node.json" } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["ES2023"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": [ 10 | "./src/*" 11 | ] 12 | }, 13 | 14 | /* Bundler mode */ 15 | "moduleResolution": "bundler", 16 | "allowImportingTsExtensions": true, 17 | "isolatedModules": true, 18 | "moduleDetection": "force", 19 | "noEmit": true, 20 | 21 | /* Linting */ 22 | "strict": true, 23 | "noUnusedLocals": true, 24 | "noUnusedParameters": true, 25 | "noFallthroughCasesInSwitch": true 26 | }, 27 | "include": ["vite.config.ts"] 28 | } 29 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import react from "@vitejs/plugin-react" 3 | import eslint from 'vite-plugin-eslint'; 4 | import { defineConfig } from "vite" 5 | 6 | export default defineConfig({ 7 | plugins: [react(),eslint()], 8 | resolve: { 9 | alias: { 10 | "@": path.resolve(__dirname, "./src"), 11 | }, 12 | }, 13 | }) 14 | --------------------------------------------------------------------------------