commit
e430cb6a3a
@ -0,0 +1,24 @@ |
|||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
pnpm-debug.log* |
||||||
|
lerna-debug.log* |
||||||
|
|
||||||
|
node_modules |
||||||
|
dist |
||||||
|
dist-ssr |
||||||
|
*.local |
||||||
|
|
||||||
|
# Editor directories and files |
||||||
|
.vscode/* |
||||||
|
!.vscode/extensions.json |
||||||
|
.idea |
||||||
|
.DS_Store |
||||||
|
*.suo |
||||||
|
*.ntvs* |
||||||
|
*.njsproj |
||||||
|
*.sln |
||||||
|
*.sw? |
@ -0,0 +1,69 @@ |
|||||||
|
# React + TypeScript + Vite |
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. |
||||||
|
|
||||||
|
Currently, two official plugins are available: |
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh |
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh |
||||||
|
|
||||||
|
## Expanding the ESLint configuration |
||||||
|
|
||||||
|
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: |
||||||
|
|
||||||
|
```js |
||||||
|
export default tseslint.config([ |
||||||
|
globalIgnores(['dist']), |
||||||
|
{ |
||||||
|
files: ['**/*.{ts,tsx}'], |
||||||
|
extends: [ |
||||||
|
// Other configs... |
||||||
|
|
||||||
|
// Remove tseslint.configs.recommended and replace with this |
||||||
|
...tseslint.configs.recommendedTypeChecked, |
||||||
|
// Alternatively, use this for stricter rules |
||||||
|
...tseslint.configs.strictTypeChecked, |
||||||
|
// Optionally, add this for stylistic rules |
||||||
|
...tseslint.configs.stylisticTypeChecked, |
||||||
|
|
||||||
|
// Other configs... |
||||||
|
], |
||||||
|
languageOptions: { |
||||||
|
parserOptions: { |
||||||
|
project: ['./tsconfig.node.json', './tsconfig.app.json'], |
||||||
|
tsconfigRootDir: import.meta.dirname, |
||||||
|
}, |
||||||
|
// other options... |
||||||
|
}, |
||||||
|
}, |
||||||
|
]) |
||||||
|
``` |
||||||
|
|
||||||
|
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: |
||||||
|
|
||||||
|
```js |
||||||
|
// eslint.config.js |
||||||
|
import reactX from 'eslint-plugin-react-x' |
||||||
|
import reactDom from 'eslint-plugin-react-dom' |
||||||
|
|
||||||
|
export default tseslint.config([ |
||||||
|
globalIgnores(['dist']), |
||||||
|
{ |
||||||
|
files: ['**/*.{ts,tsx}'], |
||||||
|
extends: [ |
||||||
|
// Other configs... |
||||||
|
// Enable lint rules for React |
||||||
|
reactX.configs['recommended-typescript'], |
||||||
|
// Enable lint rules for React DOM |
||||||
|
reactDom.configs.recommended, |
||||||
|
], |
||||||
|
languageOptions: { |
||||||
|
parserOptions: { |
||||||
|
project: ['./tsconfig.node.json', './tsconfig.app.json'], |
||||||
|
tsconfigRootDir: import.meta.dirname, |
||||||
|
}, |
||||||
|
// other options... |
||||||
|
}, |
||||||
|
}, |
||||||
|
]) |
||||||
|
``` |
@ -0,0 +1,614 @@ |
|||||||
|
{ |
||||||
|
"lockfileVersion": 1, |
||||||
|
"workspaces": { |
||||||
|
"": { |
||||||
|
"name": "temp-project", |
||||||
|
"dependencies": { |
||||||
|
"@emotion/react": "^11.14.0", |
||||||
|
"@emotion/styled": "^11.14.1", |
||||||
|
"@mui/icons-material": "^7.2.0", |
||||||
|
"@mui/material": "^7.2.0", |
||||||
|
"qrcode.react": "^4.2.0", |
||||||
|
"react": "^19.1.0", |
||||||
|
"react-dom": "^19.1.0", |
||||||
|
"react-router-dom": "^7.6.3", |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@eslint/js": "^9.30.1", |
||||||
|
"@types/react": "^19.1.8", |
||||||
|
"@types/react-dom": "^19.1.6", |
||||||
|
"@vitejs/plugin-react": "^4.6.0", |
||||||
|
"eslint": "^9.30.1", |
||||||
|
"eslint-plugin-react-hooks": "^5.2.0", |
||||||
|
"eslint-plugin-react-refresh": "^0.4.20", |
||||||
|
"globals": "^16.3.0", |
||||||
|
"typescript": "~5.8.3", |
||||||
|
"typescript-eslint": "^8.35.1", |
||||||
|
"vite": "^6.0.0", |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
"packages": { |
||||||
|
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], |
||||||
|
|
||||||
|
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], |
||||||
|
|
||||||
|
"@babel/compat-data": ["@babel/compat-data@7.28.0", "", {}, "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="], |
||||||
|
|
||||||
|
"@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], |
||||||
|
|
||||||
|
"@babel/generator": ["@babel/generator@7.28.0", "", { "dependencies": { "@babel/parser": "^7.28.0", "@babel/types": "^7.28.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg=="], |
||||||
|
|
||||||
|
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], |
||||||
|
|
||||||
|
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], |
||||||
|
|
||||||
|
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], |
||||||
|
|
||||||
|
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.27.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.27.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg=="], |
||||||
|
|
||||||
|
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], |
||||||
|
|
||||||
|
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], |
||||||
|
|
||||||
|
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], |
||||||
|
|
||||||
|
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], |
||||||
|
|
||||||
|
"@babel/helpers": ["@babel/helpers@7.27.6", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" } }, "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug=="], |
||||||
|
|
||||||
|
"@babel/parser": ["@babel/parser@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.0" }, "bin": "./bin/babel-parser.js" }, "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g=="], |
||||||
|
|
||||||
|
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], |
||||||
|
|
||||||
|
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], |
||||||
|
|
||||||
|
"@babel/runtime": ["@babel/runtime@7.27.6", "", {}, "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q=="], |
||||||
|
|
||||||
|
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], |
||||||
|
|
||||||
|
"@babel/traverse": ["@babel/traverse@7.28.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/types": "^7.28.0", "debug": "^4.3.1" } }, "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg=="], |
||||||
|
|
||||||
|
"@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="], |
||||||
|
|
||||||
|
"@emotion/babel-plugin": ["@emotion/babel-plugin@11.13.5", "", { "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", "stylis": "4.2.0" } }, "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ=="], |
||||||
|
|
||||||
|
"@emotion/cache": ["@emotion/cache@11.14.0", "", { "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA=="], |
||||||
|
|
||||||
|
"@emotion/hash": ["@emotion/hash@0.9.2", "", {}, "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="], |
||||||
|
|
||||||
|
"@emotion/is-prop-valid": ["@emotion/is-prop-valid@1.3.1", "", { "dependencies": { "@emotion/memoize": "^0.9.0" } }, "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw=="], |
||||||
|
|
||||||
|
"@emotion/memoize": ["@emotion/memoize@0.9.0", "", {}, "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="], |
||||||
|
|
||||||
|
"@emotion/react": ["@emotion/react@11.14.0", "", { "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", "@emotion/cache": "^11.14.0", "@emotion/serialize": "^1.3.3", "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA=="], |
||||||
|
|
||||||
|
"@emotion/serialize": ["@emotion/serialize@1.3.3", "", { "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/unitless": "^0.10.0", "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA=="], |
||||||
|
|
||||||
|
"@emotion/sheet": ["@emotion/sheet@1.4.0", "", {}, "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="], |
||||||
|
|
||||||
|
"@emotion/styled": ["@emotion/styled@11.14.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", "@emotion/is-prop-valid": "^1.3.0", "@emotion/serialize": "^1.3.3", "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", "@emotion/utils": "^1.4.2" }, "peerDependencies": { "@emotion/react": "^11.0.0-rc.0", "react": ">=16.8.0" } }, "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw=="], |
||||||
|
|
||||||
|
"@emotion/unitless": ["@emotion/unitless@0.10.0", "", {}, "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="], |
||||||
|
|
||||||
|
"@emotion/use-insertion-effect-with-fallbacks": ["@emotion/use-insertion-effect-with-fallbacks@1.2.0", "", { "peerDependencies": { "react": ">=16.8.0" } }, "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg=="], |
||||||
|
|
||||||
|
"@emotion/utils": ["@emotion/utils@1.4.2", "", {}, "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA=="], |
||||||
|
|
||||||
|
"@emotion/weak-memoize": ["@emotion/weak-memoize@0.4.0", "", {}, "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="], |
||||||
|
|
||||||
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.6", "", { "os": "aix", "cpu": "ppc64" }, "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw=="], |
||||||
|
|
||||||
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.6", "", { "os": "android", "cpu": "arm" }, "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg=="], |
||||||
|
|
||||||
|
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.6", "", { "os": "android", "cpu": "arm64" }, "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA=="], |
||||||
|
|
||||||
|
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.6", "", { "os": "android", "cpu": "x64" }, "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A=="], |
||||||
|
|
||||||
|
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA=="], |
||||||
|
|
||||||
|
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg=="], |
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.6", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg=="], |
||||||
|
|
||||||
|
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.6", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ=="], |
||||||
|
|
||||||
|
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.6", "", { "os": "linux", "cpu": "arm" }, "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw=="], |
||||||
|
|
||||||
|
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ=="], |
||||||
|
|
||||||
|
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.6", "", { "os": "linux", "cpu": "ia32" }, "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw=="], |
||||||
|
|
||||||
|
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.6", "", { "os": "linux", "cpu": "none" }, "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg=="], |
||||||
|
|
||||||
|
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.6", "", { "os": "linux", "cpu": "none" }, "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw=="], |
||||||
|
|
||||||
|
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.6", "", { "os": "linux", "cpu": "ppc64" }, "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw=="], |
||||||
|
|
||||||
|
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.6", "", { "os": "linux", "cpu": "none" }, "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w=="], |
||||||
|
|
||||||
|
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.6", "", { "os": "linux", "cpu": "s390x" }, "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw=="], |
||||||
|
|
||||||
|
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.6", "", { "os": "linux", "cpu": "x64" }, "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig=="], |
||||||
|
|
||||||
|
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.6", "", { "os": "none", "cpu": "arm64" }, "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q=="], |
||||||
|
|
||||||
|
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.6", "", { "os": "none", "cpu": "x64" }, "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g=="], |
||||||
|
|
||||||
|
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.6", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg=="], |
||||||
|
|
||||||
|
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.6", "", { "os": "openbsd", "cpu": "x64" }, "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw=="], |
||||||
|
|
||||||
|
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.6", "", { "os": "none", "cpu": "arm64" }, "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA=="], |
||||||
|
|
||||||
|
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.6", "", { "os": "sunos", "cpu": "x64" }, "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA=="], |
||||||
|
|
||||||
|
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q=="], |
||||||
|
|
||||||
|
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ=="], |
||||||
|
|
||||||
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA=="], |
||||||
|
|
||||||
|
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], |
||||||
|
|
||||||
|
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], |
||||||
|
|
||||||
|
"@eslint/config-array": ["@eslint/config-array@0.21.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ=="], |
||||||
|
|
||||||
|
"@eslint/config-helpers": ["@eslint/config-helpers@0.3.0", "", {}, "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw=="], |
||||||
|
|
||||||
|
"@eslint/core": ["@eslint/core@0.15.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA=="], |
||||||
|
|
||||||
|
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], |
||||||
|
|
||||||
|
"@eslint/js": ["@eslint/js@9.31.0", "", {}, "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw=="], |
||||||
|
|
||||||
|
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], |
||||||
|
|
||||||
|
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.3", "", { "dependencies": { "@eslint/core": "^0.15.1", "levn": "^0.4.1" } }, "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag=="], |
||||||
|
|
||||||
|
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], |
||||||
|
|
||||||
|
"@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], |
||||||
|
|
||||||
|
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], |
||||||
|
|
||||||
|
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], |
||||||
|
|
||||||
|
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="], |
||||||
|
|
||||||
|
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], |
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], |
||||||
|
|
||||||
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], |
||||||
|
|
||||||
|
"@mui/core-downloads-tracker": ["@mui/core-downloads-tracker@7.2.0", "", {}, "sha512-d49s7kEgI5iX40xb2YPazANvo7Bx0BLg/MNRwv+7BVpZUzXj1DaVCKlQTDex3gy/0jsCb4w7AY2uH4t4AJvSog=="], |
||||||
|
|
||||||
|
"@mui/icons-material": ["@mui/icons-material@7.2.0", "", { "dependencies": { "@babel/runtime": "^7.27.6" }, "peerDependencies": { "@mui/material": "^7.2.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-gRCspp3pfjHQyTmSOmYw7kUQTd9Udpdan4R8EnZvqPeoAtHnPzkvjBrBqzKaoAbbBp5bGF7BcD18zZJh4nwu0A=="], |
||||||
|
|
||||||
|
"@mui/material": ["@mui/material@7.2.0", "", { "dependencies": { "@babel/runtime": "^7.27.6", "@mui/core-downloads-tracker": "^7.2.0", "@mui/system": "^7.2.0", "@mui/types": "^7.4.4", "@mui/utils": "^7.2.0", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1", "react-is": "^19.1.0", "react-transition-group": "^4.4.5" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", "@mui/material-pigment-css": "^7.2.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled", "@mui/material-pigment-css", "@types/react"] }, "sha512-NTuyFNen5Z2QY+I242MDZzXnFIVIR6ERxo7vntFi9K1wCgSwvIl0HcAO2OOydKqqKApE6omRiYhpny1ZhGuH7Q=="], |
||||||
|
|
||||||
|
"@mui/private-theming": ["@mui/private-theming@7.2.0", "", { "dependencies": { "@babel/runtime": "^7.27.6", "@mui/utils": "^7.2.0", "prop-types": "^15.8.1" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-y6N1Yt3T5RMxVFnCh6+zeSWBuQdNDm5/UlM0EAYZzZR/1u+XKJWYQmbpx4e+F+1EpkYi3Nk8KhPiQDi83M3zIw=="], |
||||||
|
|
||||||
|
"@mui/styled-engine": ["@mui/styled-engine@7.2.0", "", { "dependencies": { "@babel/runtime": "^7.27.6", "@emotion/cache": "^11.14.0", "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "peerDependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled"] }, "sha512-yq08xynbrNYcB1nBcW9Fn8/h/iniM3ewRguGJXPIAbHvxEF7Pz95kbEEOAAhwzxMX4okhzvHmk0DFuC5ayvgIQ=="], |
||||||
|
|
||||||
|
"@mui/system": ["@mui/system@7.2.0", "", { "dependencies": { "@babel/runtime": "^7.27.6", "@mui/private-theming": "^7.2.0", "@mui/styled-engine": "^7.2.0", "@mui/types": "^7.4.4", "@mui/utils": "^7.2.0", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled", "@types/react"] }, "sha512-PG7cm/WluU6RAs+gNND2R9vDwNh+ERWxPkqTaiXQJGIFAyJ+VxhyKfzpdZNk0z0XdmBxxi9KhFOpgxjehf/O0A=="], |
||||||
|
|
||||||
|
"@mui/types": ["@mui/types@7.4.4", "", { "dependencies": { "@babel/runtime": "^7.27.6" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-p63yhbX52MO/ajXC7hDHJA5yjzJekvWD3q4YDLl1rSg+OXLczMYPvTuSuviPRCgRX8+E42RXz1D/dz9SxPSlWg=="], |
||||||
|
|
||||||
|
"@mui/utils": ["@mui/utils@7.2.0", "", { "dependencies": { "@babel/runtime": "^7.27.6", "@mui/types": "^7.4.4", "@types/prop-types": "^15.7.15", "clsx": "^2.1.1", "prop-types": "^15.8.1", "react-is": "^19.1.0" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-O0i1GQL6MDzhKdy9iAu5Yr0Sz1wZjROH1o3aoztuivdCXqEeQYnEjTDiRLGuFxI9zrUbTHBwobMyQH5sNtyacw=="], |
||||||
|
|
||||||
|
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], |
||||||
|
|
||||||
|
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], |
||||||
|
|
||||||
|
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], |
||||||
|
|
||||||
|
"@popperjs/core": ["@popperjs/core@2.11.8", "", {}, "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="], |
||||||
|
|
||||||
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.19", "", {}, "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA=="], |
||||||
|
|
||||||
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.45.1", "", { "os": "android", "cpu": "arm" }, "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA=="], |
||||||
|
|
||||||
|
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.45.1", "", { "os": "android", "cpu": "arm64" }, "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ=="], |
||||||
|
|
||||||
|
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.45.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA=="], |
||||||
|
|
||||||
|
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.45.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og=="], |
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.45.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g=="], |
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.45.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A=="], |
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q=="], |
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q=="], |
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw=="], |
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog=="], |
||||||
|
|
||||||
|
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg=="], |
||||||
|
|
||||||
|
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.45.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg=="], |
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw=="], |
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA=="], |
||||||
|
|
||||||
|
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.45.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw=="], |
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw=="], |
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw=="], |
||||||
|
|
||||||
|
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.45.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg=="], |
||||||
|
|
||||||
|
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.45.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw=="], |
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.45.1", "", { "os": "win32", "cpu": "x64" }, "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA=="], |
||||||
|
|
||||||
|
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], |
||||||
|
|
||||||
|
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], |
||||||
|
|
||||||
|
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], |
||||||
|
|
||||||
|
"@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="], |
||||||
|
|
||||||
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], |
||||||
|
|
||||||
|
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], |
||||||
|
|
||||||
|
"@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="], |
||||||
|
|
||||||
|
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], |
||||||
|
|
||||||
|
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], |
||||||
|
|
||||||
|
"@types/react-dom": ["@types/react-dom@19.1.6", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw=="], |
||||||
|
|
||||||
|
"@types/react-transition-group": ["@types/react-transition-group@4.4.12", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w=="], |
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.37.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/type-utils": "8.37.0", "@typescript-eslint/utils": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.37.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA=="], |
||||||
|
|
||||||
|
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.37.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA=="], |
||||||
|
|
||||||
|
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.37.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.37.0", "@typescript-eslint/types": "^8.37.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA=="], |
||||||
|
|
||||||
|
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0" } }, "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA=="], |
||||||
|
|
||||||
|
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.37.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg=="], |
||||||
|
|
||||||
|
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0", "@typescript-eslint/utils": "8.37.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow=="], |
||||||
|
|
||||||
|
"@typescript-eslint/types": ["@typescript-eslint/types@8.37.0", "", {}, "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ=="], |
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.37.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.37.0", "@typescript-eslint/tsconfig-utils": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg=="], |
||||||
|
|
||||||
|
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.37.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A=="], |
||||||
|
|
||||||
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w=="], |
||||||
|
|
||||||
|
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.6.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.19", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ=="], |
||||||
|
|
||||||
|
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], |
||||||
|
|
||||||
|
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], |
||||||
|
|
||||||
|
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], |
||||||
|
|
||||||
|
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], |
||||||
|
|
||||||
|
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], |
||||||
|
|
||||||
|
"babel-plugin-macros": ["babel-plugin-macros@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="], |
||||||
|
|
||||||
|
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], |
||||||
|
|
||||||
|
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], |
||||||
|
|
||||||
|
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], |
||||||
|
|
||||||
|
"browserslist": ["browserslist@4.25.1", "", { "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw=="], |
||||||
|
|
||||||
|
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], |
||||||
|
|
||||||
|
"caniuse-lite": ["caniuse-lite@1.0.30001727", "", {}, "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q=="], |
||||||
|
|
||||||
|
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], |
||||||
|
|
||||||
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], |
||||||
|
|
||||||
|
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], |
||||||
|
|
||||||
|
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], |
||||||
|
|
||||||
|
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], |
||||||
|
|
||||||
|
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], |
||||||
|
|
||||||
|
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], |
||||||
|
|
||||||
|
"cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="], |
||||||
|
|
||||||
|
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], |
||||||
|
|
||||||
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], |
||||||
|
|
||||||
|
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], |
||||||
|
|
||||||
|
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], |
||||||
|
|
||||||
|
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="], |
||||||
|
|
||||||
|
"electron-to-chromium": ["electron-to-chromium@1.5.185", "", {}, "sha512-dYOZfUk57hSMPePoIQ1fZWl1Fkj+OshhEVuPacNKWzC1efe56OsHY3l/jCfiAgIICOU3VgOIdoq7ahg7r7n6MQ=="], |
||||||
|
|
||||||
|
"error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="], |
||||||
|
|
||||||
|
"esbuild": ["esbuild@0.25.6", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.6", "@esbuild/android-arm": "0.25.6", "@esbuild/android-arm64": "0.25.6", "@esbuild/android-x64": "0.25.6", "@esbuild/darwin-arm64": "0.25.6", "@esbuild/darwin-x64": "0.25.6", "@esbuild/freebsd-arm64": "0.25.6", "@esbuild/freebsd-x64": "0.25.6", "@esbuild/linux-arm": "0.25.6", "@esbuild/linux-arm64": "0.25.6", "@esbuild/linux-ia32": "0.25.6", "@esbuild/linux-loong64": "0.25.6", "@esbuild/linux-mips64el": "0.25.6", "@esbuild/linux-ppc64": "0.25.6", "@esbuild/linux-riscv64": "0.25.6", "@esbuild/linux-s390x": "0.25.6", "@esbuild/linux-x64": "0.25.6", "@esbuild/netbsd-arm64": "0.25.6", "@esbuild/netbsd-x64": "0.25.6", "@esbuild/openbsd-arm64": "0.25.6", "@esbuild/openbsd-x64": "0.25.6", "@esbuild/openharmony-arm64": "0.25.6", "@esbuild/sunos-x64": "0.25.6", "@esbuild/win32-arm64": "0.25.6", "@esbuild/win32-ia32": "0.25.6", "@esbuild/win32-x64": "0.25.6" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg=="], |
||||||
|
|
||||||
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], |
||||||
|
|
||||||
|
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], |
||||||
|
|
||||||
|
"eslint": ["eslint@9.31.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.31.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ=="], |
||||||
|
|
||||||
|
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="], |
||||||
|
|
||||||
|
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.20", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA=="], |
||||||
|
|
||||||
|
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], |
||||||
|
|
||||||
|
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], |
||||||
|
|
||||||
|
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], |
||||||
|
|
||||||
|
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], |
||||||
|
|
||||||
|
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], |
||||||
|
|
||||||
|
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], |
||||||
|
|
||||||
|
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], |
||||||
|
|
||||||
|
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], |
||||||
|
|
||||||
|
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], |
||||||
|
|
||||||
|
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], |
||||||
|
|
||||||
|
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], |
||||||
|
|
||||||
|
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], |
||||||
|
|
||||||
|
"fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="], |
||||||
|
|
||||||
|
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], |
||||||
|
|
||||||
|
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], |
||||||
|
|
||||||
|
"find-root": ["find-root@1.1.0", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="], |
||||||
|
|
||||||
|
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], |
||||||
|
|
||||||
|
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], |
||||||
|
|
||||||
|
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], |
||||||
|
|
||||||
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], |
||||||
|
|
||||||
|
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], |
||||||
|
|
||||||
|
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], |
||||||
|
|
||||||
|
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], |
||||||
|
|
||||||
|
"globals": ["globals@16.3.0", "", {}, "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ=="], |
||||||
|
|
||||||
|
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], |
||||||
|
|
||||||
|
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], |
||||||
|
|
||||||
|
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], |
||||||
|
|
||||||
|
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="], |
||||||
|
|
||||||
|
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], |
||||||
|
|
||||||
|
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], |
||||||
|
|
||||||
|
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], |
||||||
|
|
||||||
|
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], |
||||||
|
|
||||||
|
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], |
||||||
|
|
||||||
|
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], |
||||||
|
|
||||||
|
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], |
||||||
|
|
||||||
|
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], |
||||||
|
|
||||||
|
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], |
||||||
|
|
||||||
|
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], |
||||||
|
|
||||||
|
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], |
||||||
|
|
||||||
|
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], |
||||||
|
|
||||||
|
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], |
||||||
|
|
||||||
|
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], |
||||||
|
|
||||||
|
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], |
||||||
|
|
||||||
|
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], |
||||||
|
|
||||||
|
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], |
||||||
|
|
||||||
|
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], |
||||||
|
|
||||||
|
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], |
||||||
|
|
||||||
|
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], |
||||||
|
|
||||||
|
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], |
||||||
|
|
||||||
|
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], |
||||||
|
|
||||||
|
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], |
||||||
|
|
||||||
|
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], |
||||||
|
|
||||||
|
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], |
||||||
|
|
||||||
|
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], |
||||||
|
|
||||||
|
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], |
||||||
|
|
||||||
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], |
||||||
|
|
||||||
|
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], |
||||||
|
|
||||||
|
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], |
||||||
|
|
||||||
|
"node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], |
||||||
|
|
||||||
|
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], |
||||||
|
|
||||||
|
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], |
||||||
|
|
||||||
|
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], |
||||||
|
|
||||||
|
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], |
||||||
|
|
||||||
|
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], |
||||||
|
|
||||||
|
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], |
||||||
|
|
||||||
|
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], |
||||||
|
|
||||||
|
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], |
||||||
|
|
||||||
|
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], |
||||||
|
|
||||||
|
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], |
||||||
|
|
||||||
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], |
||||||
|
|
||||||
|
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], |
||||||
|
|
||||||
|
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], |
||||||
|
|
||||||
|
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], |
||||||
|
|
||||||
|
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], |
||||||
|
|
||||||
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], |
||||||
|
|
||||||
|
"qrcode.react": ["qrcode.react@4.2.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA=="], |
||||||
|
|
||||||
|
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], |
||||||
|
|
||||||
|
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], |
||||||
|
|
||||||
|
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="], |
||||||
|
|
||||||
|
"react-is": ["react-is@19.1.0", "", {}, "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg=="], |
||||||
|
|
||||||
|
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], |
||||||
|
|
||||||
|
"react-router": ["react-router@7.6.3", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA=="], |
||||||
|
|
||||||
|
"react-router-dom": ["react-router-dom@7.6.3", "", { "dependencies": { "react-router": "7.6.3" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-DiWJm9qdUAmiJrVWaeJdu4TKu13+iB/8IEi0EW/XgaHCjW/vWGrwzup0GVvaMteuZjKnh5bEvJP/K0MDnzawHw=="], |
||||||
|
|
||||||
|
"react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="], |
||||||
|
|
||||||
|
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], |
||||||
|
|
||||||
|
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], |
||||||
|
|
||||||
|
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], |
||||||
|
|
||||||
|
"rollup": ["rollup@4.45.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.45.1", "@rollup/rollup-android-arm64": "4.45.1", "@rollup/rollup-darwin-arm64": "4.45.1", "@rollup/rollup-darwin-x64": "4.45.1", "@rollup/rollup-freebsd-arm64": "4.45.1", "@rollup/rollup-freebsd-x64": "4.45.1", "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", "@rollup/rollup-linux-arm-musleabihf": "4.45.1", "@rollup/rollup-linux-arm64-gnu": "4.45.1", "@rollup/rollup-linux-arm64-musl": "4.45.1", "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-musl": "4.45.1", "@rollup/rollup-linux-s390x-gnu": "4.45.1", "@rollup/rollup-linux-x64-gnu": "4.45.1", "@rollup/rollup-linux-x64-musl": "4.45.1", "@rollup/rollup-win32-arm64-msvc": "4.45.1", "@rollup/rollup-win32-ia32-msvc": "4.45.1", "@rollup/rollup-win32-x64-msvc": "4.45.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw=="], |
||||||
|
|
||||||
|
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], |
||||||
|
|
||||||
|
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], |
||||||
|
|
||||||
|
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], |
||||||
|
|
||||||
|
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="], |
||||||
|
|
||||||
|
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], |
||||||
|
|
||||||
|
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], |
||||||
|
|
||||||
|
"source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="], |
||||||
|
|
||||||
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], |
||||||
|
|
||||||
|
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], |
||||||
|
|
||||||
|
"stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="], |
||||||
|
|
||||||
|
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], |
||||||
|
|
||||||
|
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], |
||||||
|
|
||||||
|
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], |
||||||
|
|
||||||
|
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], |
||||||
|
|
||||||
|
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], |
||||||
|
|
||||||
|
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], |
||||||
|
|
||||||
|
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], |
||||||
|
|
||||||
|
"typescript-eslint": ["typescript-eslint@8.37.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.37.0", "@typescript-eslint/parser": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0", "@typescript-eslint/utils": "8.37.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA=="], |
||||||
|
|
||||||
|
"update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], |
||||||
|
|
||||||
|
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], |
||||||
|
|
||||||
|
"vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="], |
||||||
|
|
||||||
|
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], |
||||||
|
|
||||||
|
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], |
||||||
|
|
||||||
|
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], |
||||||
|
|
||||||
|
"yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="], |
||||||
|
|
||||||
|
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], |
||||||
|
|
||||||
|
"@emotion/babel-plugin/convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="], |
||||||
|
|
||||||
|
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], |
||||||
|
|
||||||
|
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], |
||||||
|
|
||||||
|
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], |
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], |
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], |
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], |
||||||
|
|
||||||
|
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], |
||||||
|
|
||||||
|
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], |
||||||
|
|
||||||
|
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], |
||||||
|
|
||||||
|
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], |
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,90 @@ |
|||||||
|
[ |
||||||
|
{ |
||||||
|
"id": "1", |
||||||
|
"name": "John Smith", |
||||||
|
"email": "john.smith@example.com", |
||||||
|
"phone": "+1 (555) 123-4567", |
||||||
|
"company": "Tech Corp", |
||||||
|
"position": "Senior Developer", |
||||||
|
"source": "linkedin", |
||||||
|
"profileImage": "https://i.pravatar.cc/150?img=1", |
||||||
|
"linkedinUrl": "https://linkedin.com/in/johnsmith", |
||||||
|
"notes": "Met at React conference 2023", |
||||||
|
"tags": ["developer", "react", "frontend"], |
||||||
|
"createdAt": "2023-10-01T10:00:00Z", |
||||||
|
"updatedAt": "2023-10-15T14:30:00Z" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "2", |
||||||
|
"name": "Sarah Johnson", |
||||||
|
"email": "sarah.johnson@startup.io", |
||||||
|
"phone": "+1 (555) 987-6543", |
||||||
|
"company": "Startup Inc", |
||||||
|
"position": "Product Manager", |
||||||
|
"source": "linkedin", |
||||||
|
"profileImage": "https://i.pravatar.cc/150?img=2", |
||||||
|
"linkedinUrl": "https://linkedin.com/in/sarahjohnson", |
||||||
|
"notes": "Potential collaboration on new project", |
||||||
|
"tags": ["product", "startup", "management"], |
||||||
|
"createdAt": "2023-09-15T09:00:00Z", |
||||||
|
"updatedAt": "2023-10-20T16:45:00Z" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "3", |
||||||
|
"name": "Mike Chen", |
||||||
|
"email": "mike.chen@gmail.com", |
||||||
|
"phone": "+1 (555) 456-7890", |
||||||
|
"company": "Freelance", |
||||||
|
"position": "UX Designer", |
||||||
|
"source": "contacts", |
||||||
|
"profileImage": "https://i.pravatar.cc/150?img=3", |
||||||
|
"notes": "Design consultant for mobile app", |
||||||
|
"tags": ["design", "ux", "mobile"], |
||||||
|
"createdAt": "2023-08-20T11:30:00Z", |
||||||
|
"updatedAt": "2023-09-10T13:15:00Z" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "4", |
||||||
|
"name": "Emily Rodriguez", |
||||||
|
"email": "emily.rodriguez@bigtech.com", |
||||||
|
"phone": "+1 (555) 234-5678", |
||||||
|
"company": "Big Tech Corp", |
||||||
|
"position": "Engineering Manager", |
||||||
|
"source": "linkedin", |
||||||
|
"profileImage": "https://i.pravatar.cc/150?img=4", |
||||||
|
"linkedinUrl": "https://linkedin.com/in/emilyrodriguez", |
||||||
|
"notes": "Former colleague from previous company", |
||||||
|
"tags": ["engineering", "management", "scaling"], |
||||||
|
"createdAt": "2023-07-10T08:00:00Z", |
||||||
|
"updatedAt": "2023-08-25T10:20:00Z" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "5", |
||||||
|
"name": "David Wilson", |
||||||
|
"email": "david.wilson@consulting.com", |
||||||
|
"phone": "+1 (555) 345-6789", |
||||||
|
"company": "Wilson Consulting", |
||||||
|
"position": "Business Consultant", |
||||||
|
"source": "contacts", |
||||||
|
"profileImage": "https://i.pravatar.cc/150?img=5", |
||||||
|
"notes": "Helped with business strategy", |
||||||
|
"tags": ["consulting", "strategy", "business"], |
||||||
|
"createdAt": "2023-06-05T14:00:00Z", |
||||||
|
"updatedAt": "2023-07-15T09:30:00Z" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "6", |
||||||
|
"name": "Lisa Thompson", |
||||||
|
"email": "lisa.thompson@marketing.co", |
||||||
|
"phone": "+1 (555) 567-8901", |
||||||
|
"company": "Marketing Co", |
||||||
|
"position": "Marketing Director", |
||||||
|
"source": "linkedin", |
||||||
|
"profileImage": "https://i.pravatar.cc/150?img=6", |
||||||
|
"linkedinUrl": "https://linkedin.com/in/lisathompson", |
||||||
|
"notes": "Great at digital marketing strategies", |
||||||
|
"tags": ["marketing", "digital", "strategy"], |
||||||
|
"createdAt": "2023-05-12T12:00:00Z", |
||||||
|
"updatedAt": "2023-06-20T15:45:00Z" |
||||||
|
} |
||||||
|
] |
@ -0,0 +1,18 @@ |
|||||||
|
[ |
||||||
|
{ |
||||||
|
"id": "linkedin", |
||||||
|
"name": "LinkedIn", |
||||||
|
"type": "linkedin", |
||||||
|
"icon": "LinkedInIcon", |
||||||
|
"description": "Import your LinkedIn connections", |
||||||
|
"isAvailable": true |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "contacts", |
||||||
|
"name": "Contacts", |
||||||
|
"type": "contacts", |
||||||
|
"icon": "ContactsIcon", |
||||||
|
"description": "Import from your device contacts", |
||||||
|
"isAvailable": true |
||||||
|
} |
||||||
|
] |
@ -0,0 +1,23 @@ |
|||||||
|
import js from '@eslint/js' |
||||||
|
import globals from 'globals' |
||||||
|
import reactHooks from 'eslint-plugin-react-hooks' |
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh' |
||||||
|
import tseslint from 'typescript-eslint' |
||||||
|
import { globalIgnores } from 'eslint/config' |
||||||
|
|
||||||
|
export default tseslint.config([ |
||||||
|
globalIgnores(['dist']), |
||||||
|
{ |
||||||
|
files: ['**/*.{ts,tsx}'], |
||||||
|
extends: [ |
||||||
|
js.configs.recommended, |
||||||
|
tseslint.configs.recommended, |
||||||
|
reactHooks.configs['recommended-latest'], |
||||||
|
reactRefresh.configs.vite, |
||||||
|
], |
||||||
|
languageOptions: { |
||||||
|
ecmaVersion: 2020, |
||||||
|
globals: globals.browser, |
||||||
|
}, |
||||||
|
}, |
||||||
|
]) |
@ -0,0 +1,13 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8" /> |
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||||
|
<title>Vite + React + TS</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="root"></div> |
||||||
|
<script type="module" src="/src/main.tsx"></script> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,35 @@ |
|||||||
|
{ |
||||||
|
"name": "temp-project", |
||||||
|
"private": true, |
||||||
|
"version": "0.0.0", |
||||||
|
"type": "module", |
||||||
|
"scripts": { |
||||||
|
"dev": "vite", |
||||||
|
"build": "tsc -b && vite build", |
||||||
|
"lint": "eslint .", |
||||||
|
"preview": "vite preview" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"@emotion/react": "^11.14.0", |
||||||
|
"@emotion/styled": "^11.14.1", |
||||||
|
"@mui/icons-material": "^7.2.0", |
||||||
|
"@mui/material": "^7.2.0", |
||||||
|
"qrcode.react": "^4.2.0", |
||||||
|
"react": "^19.1.0", |
||||||
|
"react-dom": "^19.1.0", |
||||||
|
"react-router-dom": "^7.6.3" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@eslint/js": "^9.30.1", |
||||||
|
"@types/react": "^19.1.8", |
||||||
|
"@types/react-dom": "^19.1.6", |
||||||
|
"@vitejs/plugin-react": "^4.6.0", |
||||||
|
"eslint": "^9.30.1", |
||||||
|
"eslint-plugin-react-hooks": "^5.2.0", |
||||||
|
"eslint-plugin-react-refresh": "^0.4.20", |
||||||
|
"globals": "^16.3.0", |
||||||
|
"typescript": "~5.8.3", |
||||||
|
"typescript-eslint": "^8.35.1", |
||||||
|
"vite": "^6.0.0" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,90 @@ |
|||||||
|
[ |
||||||
|
{ |
||||||
|
"id": "1", |
||||||
|
"name": "John Smith", |
||||||
|
"email": "john.smith@example.com", |
||||||
|
"phone": "+1 (555) 123-4567", |
||||||
|
"company": "Tech Corp", |
||||||
|
"position": "Senior Developer", |
||||||
|
"source": "linkedin", |
||||||
|
"profileImage": "https://i.pravatar.cc/150?img=1", |
||||||
|
"linkedinUrl": "https://linkedin.com/in/johnsmith", |
||||||
|
"notes": "Met at React conference 2023", |
||||||
|
"tags": ["developer", "react", "frontend"], |
||||||
|
"createdAt": "2023-10-01T10:00:00Z", |
||||||
|
"updatedAt": "2023-10-15T14:30:00Z" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "2", |
||||||
|
"name": "Sarah Johnson", |
||||||
|
"email": "sarah.johnson@startup.io", |
||||||
|
"phone": "+1 (555) 987-6543", |
||||||
|
"company": "Startup Inc", |
||||||
|
"position": "Product Manager", |
||||||
|
"source": "linkedin", |
||||||
|
"profileImage": "https://i.pravatar.cc/150?img=2", |
||||||
|
"linkedinUrl": "https://linkedin.com/in/sarahjohnson", |
||||||
|
"notes": "Potential collaboration on new project", |
||||||
|
"tags": ["product", "startup", "management"], |
||||||
|
"createdAt": "2023-09-15T09:00:00Z", |
||||||
|
"updatedAt": "2023-10-20T16:45:00Z" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "3", |
||||||
|
"name": "Mike Chen", |
||||||
|
"email": "mike.chen@gmail.com", |
||||||
|
"phone": "+1 (555) 456-7890", |
||||||
|
"company": "Freelance", |
||||||
|
"position": "UX Designer", |
||||||
|
"source": "contacts", |
||||||
|
"profileImage": "https://i.pravatar.cc/150?img=3", |
||||||
|
"notes": "Design consultant for mobile app", |
||||||
|
"tags": ["design", "ux", "mobile"], |
||||||
|
"createdAt": "2023-08-20T11:30:00Z", |
||||||
|
"updatedAt": "2023-09-10T13:15:00Z" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "4", |
||||||
|
"name": "Emily Rodriguez", |
||||||
|
"email": "emily.rodriguez@bigtech.com", |
||||||
|
"phone": "+1 (555) 234-5678", |
||||||
|
"company": "Big Tech Corp", |
||||||
|
"position": "Engineering Manager", |
||||||
|
"source": "linkedin", |
||||||
|
"profileImage": "https://i.pravatar.cc/150?img=4", |
||||||
|
"linkedinUrl": "https://linkedin.com/in/emilyrodriguez", |
||||||
|
"notes": "Former colleague from previous company", |
||||||
|
"tags": ["engineering", "management", "scaling"], |
||||||
|
"createdAt": "2023-07-10T08:00:00Z", |
||||||
|
"updatedAt": "2023-08-25T10:20:00Z" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "5", |
||||||
|
"name": "David Wilson", |
||||||
|
"email": "david.wilson@consulting.com", |
||||||
|
"phone": "+1 (555) 345-6789", |
||||||
|
"company": "Wilson Consulting", |
||||||
|
"position": "Business Consultant", |
||||||
|
"source": "contacts", |
||||||
|
"profileImage": "https://i.pravatar.cc/150?img=5", |
||||||
|
"notes": "Helped with business strategy", |
||||||
|
"tags": ["consulting", "strategy", "business"], |
||||||
|
"createdAt": "2023-06-05T14:00:00Z", |
||||||
|
"updatedAt": "2023-07-15T09:30:00Z" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "6", |
||||||
|
"name": "Lisa Thompson", |
||||||
|
"email": "lisa.thompson@marketing.co", |
||||||
|
"phone": "+1 (555) 567-8901", |
||||||
|
"company": "Marketing Co", |
||||||
|
"position": "Marketing Director", |
||||||
|
"source": "linkedin", |
||||||
|
"profileImage": "https://i.pravatar.cc/150?img=6", |
||||||
|
"linkedinUrl": "https://linkedin.com/in/lisathompson", |
||||||
|
"notes": "Great at digital marketing strategies", |
||||||
|
"tags": ["marketing", "digital", "strategy"], |
||||||
|
"createdAt": "2023-05-12T12:00:00Z", |
||||||
|
"updatedAt": "2023-06-20T15:45:00Z" |
||||||
|
} |
||||||
|
] |
@ -0,0 +1,18 @@ |
|||||||
|
[ |
||||||
|
{ |
||||||
|
"id": "linkedin", |
||||||
|
"name": "LinkedIn", |
||||||
|
"type": "linkedin", |
||||||
|
"icon": "LinkedInIcon", |
||||||
|
"description": "Import your LinkedIn connections", |
||||||
|
"isAvailable": true |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "contacts", |
||||||
|
"name": "Contacts", |
||||||
|
"type": "contacts", |
||||||
|
"icon": "ContactsIcon", |
||||||
|
"description": "Import from your device contacts", |
||||||
|
"isAvailable": true |
||||||
|
} |
||||||
|
] |
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,42 @@ |
|||||||
|
#root { |
||||||
|
max-width: 1280px; |
||||||
|
margin: 0 auto; |
||||||
|
padding: 2rem; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.logo { |
||||||
|
height: 6em; |
||||||
|
padding: 1.5em; |
||||||
|
will-change: filter; |
||||||
|
transition: filter 300ms; |
||||||
|
} |
||||||
|
.logo:hover { |
||||||
|
filter: drop-shadow(0 0 2em #646cffaa); |
||||||
|
} |
||||||
|
.logo.react:hover { |
||||||
|
filter: drop-shadow(0 0 2em #61dafbaa); |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes logo-spin { |
||||||
|
from { |
||||||
|
transform: rotate(0deg); |
||||||
|
} |
||||||
|
to { |
||||||
|
transform: rotate(360deg); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) { |
||||||
|
a:nth-of-type(2) .logo { |
||||||
|
animation: logo-spin infinite 20s linear; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.card { |
||||||
|
padding: 2em; |
||||||
|
} |
||||||
|
|
||||||
|
.read-the-docs { |
||||||
|
color: #888; |
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; |
||||||
|
import { ThemeProvider, createTheme } from '@mui/material/styles'; |
||||||
|
import CssBaseline from '@mui/material/CssBaseline'; |
||||||
|
import { AppBar, Toolbar, Typography, Box, Button } from '@mui/material'; |
||||||
|
import { QrCode, PersonAdd } from '@mui/icons-material'; |
||||||
|
import { OnboardingProvider } from './context/OnboardingContext'; |
||||||
|
import ImportPage from './pages/ImportPage'; |
||||||
|
import ContactListPage from './pages/ContactListPage'; |
||||||
|
import ContactViewPage from './pages/ContactViewPage'; |
||||||
|
import InvitationPage from './pages/InvitationPage'; |
||||||
|
import OnboardingPage from './pages/OnboardingPage'; |
||||||
|
|
||||||
|
const theme = createTheme({ |
||||||
|
palette: { |
||||||
|
primary: { |
||||||
|
main: '#1976d2', |
||||||
|
}, |
||||||
|
secondary: { |
||||||
|
main: '#dc004e', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
function App() { |
||||||
|
return ( |
||||||
|
<ThemeProvider theme={theme}> |
||||||
|
<CssBaseline /> |
||||||
|
<OnboardingProvider> |
||||||
|
<Router> |
||||||
|
<Box sx={{ flexGrow: 1 }}> |
||||||
|
<AppBar position="static"> |
||||||
|
<Toolbar> |
||||||
|
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}> |
||||||
|
Personal Network Manager |
||||||
|
</Typography> |
||||||
|
<Button |
||||||
|
color="inherit" |
||||||
|
startIcon={<QrCode />} |
||||||
|
href="/invite" |
||||||
|
sx={{ mr: 1 }} |
||||||
|
> |
||||||
|
Invite |
||||||
|
</Button> |
||||||
|
<Button |
||||||
|
color="inherit" |
||||||
|
startIcon={<PersonAdd />} |
||||||
|
href="/onboarding" |
||||||
|
> |
||||||
|
Join |
||||||
|
</Button> |
||||||
|
</Toolbar> |
||||||
|
</AppBar> |
||||||
|
<Routes> |
||||||
|
<Route path="/" element={<ImportPage />} /> |
||||||
|
<Route path="/contacts" element={<ContactListPage />} /> |
||||||
|
<Route path="/contacts/:id" element={<ContactViewPage />} /> |
||||||
|
<Route path="/invite" element={<InvitationPage />} /> |
||||||
|
<Route path="/onboarding" element={<OnboardingPage />} /> |
||||||
|
</Routes> |
||||||
|
</Box> |
||||||
|
</Router> |
||||||
|
</OnboardingProvider> |
||||||
|
</ThemeProvider> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default App; |
After Width: | Height: | Size: 4.0 KiB |
@ -0,0 +1,254 @@ |
|||||||
|
import { useState, useEffect } from 'react'; |
||||||
|
import { |
||||||
|
Box, |
||||||
|
TextField, |
||||||
|
Typography, |
||||||
|
Grid, |
||||||
|
Avatar, |
||||||
|
IconButton, |
||||||
|
Alert, |
||||||
|
InputAdornment |
||||||
|
} from '@mui/material'; |
||||||
|
import { Person, Email, Phone, Business, Work, PhotoCamera } from '@mui/icons-material'; |
||||||
|
import { useOnboarding } from '../../context/OnboardingContext'; |
||||||
|
|
||||||
|
const BasicInfoStep = () => { |
||||||
|
const { state, updateProfile } = useOnboarding(); |
||||||
|
const [localProfile, setLocalProfile] = useState(state.userProfile); |
||||||
|
const [errors, setErrors] = useState<Record<string, string>>({}); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setLocalProfile(state.userProfile); |
||||||
|
}, [state.userProfile]); |
||||||
|
|
||||||
|
const handleChange = (field: string, value: string) => { |
||||||
|
const newProfile = { ...localProfile, [field]: value }; |
||||||
|
setLocalProfile(newProfile); |
||||||
|
updateProfile(newProfile); |
||||||
|
|
||||||
|
// Clear error for this field
|
||||||
|
if (errors[field]) { |
||||||
|
setErrors(prev => ({ ...prev, [field]: '' })); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const validateEmail = (email: string) => { |
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
||||||
|
return emailRegex.test(email); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleBlur = (field: string, value: string) => { |
||||||
|
const newErrors = { ...errors }; |
||||||
|
|
||||||
|
switch (field) { |
||||||
|
case 'firstName': |
||||||
|
case 'lastName': |
||||||
|
if (!value.trim()) { |
||||||
|
newErrors[field] = `${field === 'firstName' ? 'First' : 'Last'} name is required`; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'email': |
||||||
|
if (!value.trim()) { |
||||||
|
newErrors[field] = 'Email is required'; |
||||||
|
} else if (!validateEmail(value)) { |
||||||
|
newErrors[field] = 'Please enter a valid email address'; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'phone': |
||||||
|
if (value && !/^\+?[\d\s\-\(\)]+$/.test(value)) { |
||||||
|
newErrors[field] = 'Please enter a valid phone number'; |
||||||
|
} |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
setErrors(newErrors); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleAvatarClick = () => { |
||||||
|
// In a real app, this would open a file picker
|
||||||
|
console.log('Avatar click - would open file picker'); |
||||||
|
}; |
||||||
|
|
||||||
|
const getInitials = () => { |
||||||
|
const first = localProfile.firstName || ''; |
||||||
|
const last = localProfile.lastName || ''; |
||||||
|
return `${first.charAt(0)}${last.charAt(0)}`.toUpperCase(); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Box sx={{ maxWidth: '600px', mx: 'auto' }}> |
||||||
|
<Typography variant="h5" gutterBottom align="center"> |
||||||
|
Tell us about yourself |
||||||
|
</Typography> |
||||||
|
<Typography variant="body1" color="text.secondary" align="center" sx={{ mb: 4 }}> |
||||||
|
This information helps others in your network connect with you |
||||||
|
</Typography> |
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 4 }}> |
||||||
|
<Box sx={{ position: 'relative' }}> |
||||||
|
<Avatar |
||||||
|
sx={{ |
||||||
|
width: 100, |
||||||
|
height: 100, |
||||||
|
fontSize: '2rem', |
||||||
|
cursor: 'pointer', |
||||||
|
'&:hover': { |
||||||
|
opacity: 0.8, |
||||||
|
}, |
||||||
|
}} |
||||||
|
onClick={handleAvatarClick} |
||||||
|
> |
||||||
|
{getInitials() || <Person />} |
||||||
|
</Avatar> |
||||||
|
<IconButton |
||||||
|
sx={{ |
||||||
|
position: 'absolute', |
||||||
|
bottom: 0, |
||||||
|
right: 0, |
||||||
|
bgcolor: 'background.paper', |
||||||
|
border: 1, |
||||||
|
borderColor: 'divider', |
||||||
|
'&:hover': { |
||||||
|
bgcolor: 'action.hover', |
||||||
|
}, |
||||||
|
}} |
||||||
|
size="small" |
||||||
|
onClick={handleAvatarClick} |
||||||
|
> |
||||||
|
<PhotoCamera fontSize="small" /> |
||||||
|
</IconButton> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
|
||||||
|
<Grid container spacing={3}> |
||||||
|
<Grid size={{ xs: 12, sm: 6 }}> |
||||||
|
<TextField |
||||||
|
fullWidth |
||||||
|
label="First Name" |
||||||
|
value={localProfile.firstName || ''} |
||||||
|
onChange={(e) => handleChange('firstName', e.target.value)} |
||||||
|
onBlur={(e) => handleBlur('firstName', e.target.value)} |
||||||
|
error={!!errors.firstName} |
||||||
|
helperText={errors.firstName} |
||||||
|
required |
||||||
|
InputProps={{ |
||||||
|
startAdornment: ( |
||||||
|
<InputAdornment position="start"> |
||||||
|
<Person /> |
||||||
|
</InputAdornment> |
||||||
|
), |
||||||
|
}} |
||||||
|
/> |
||||||
|
</Grid> |
||||||
|
<Grid size={{ xs: 12, sm: 6 }}> |
||||||
|
<TextField |
||||||
|
fullWidth |
||||||
|
label="Last Name" |
||||||
|
value={localProfile.lastName || ''} |
||||||
|
onChange={(e) => handleChange('lastName', e.target.value)} |
||||||
|
onBlur={(e) => handleBlur('lastName', e.target.value)} |
||||||
|
error={!!errors.lastName} |
||||||
|
helperText={errors.lastName} |
||||||
|
required |
||||||
|
InputProps={{ |
||||||
|
startAdornment: ( |
||||||
|
<InputAdornment position="start"> |
||||||
|
<Person /> |
||||||
|
</InputAdornment> |
||||||
|
), |
||||||
|
}} |
||||||
|
/> |
||||||
|
</Grid> |
||||||
|
<Grid size={{ xs: 12 }}> |
||||||
|
<TextField |
||||||
|
fullWidth |
||||||
|
label="Email Address" |
||||||
|
type="email" |
||||||
|
value={localProfile.email || ''} |
||||||
|
onChange={(e) => handleChange('email', e.target.value)} |
||||||
|
onBlur={(e) => handleBlur('email', e.target.value)} |
||||||
|
error={!!errors.email} |
||||||
|
helperText={errors.email} |
||||||
|
required |
||||||
|
InputProps={{ |
||||||
|
startAdornment: ( |
||||||
|
<InputAdornment position="start"> |
||||||
|
<Email /> |
||||||
|
</InputAdornment> |
||||||
|
), |
||||||
|
}} |
||||||
|
/> |
||||||
|
</Grid> |
||||||
|
<Grid size={{ xs: 12 }}> |
||||||
|
<TextField |
||||||
|
fullWidth |
||||||
|
label="Phone Number" |
||||||
|
value={localProfile.phone || ''} |
||||||
|
onChange={(e) => handleChange('phone', e.target.value)} |
||||||
|
onBlur={(e) => handleBlur('phone', e.target.value)} |
||||||
|
error={!!errors.phone} |
||||||
|
helperText={errors.phone} |
||||||
|
placeholder="+1 (555) 123-4567" |
||||||
|
InputProps={{ |
||||||
|
startAdornment: ( |
||||||
|
<InputAdornment position="start"> |
||||||
|
<Phone /> |
||||||
|
</InputAdornment> |
||||||
|
), |
||||||
|
}} |
||||||
|
/> |
||||||
|
</Grid> |
||||||
|
<Grid size={{ xs: 12, sm: 6 }}> |
||||||
|
<TextField |
||||||
|
fullWidth |
||||||
|
label="Company" |
||||||
|
value={localProfile.company || ''} |
||||||
|
onChange={(e) => handleChange('company', e.target.value)} |
||||||
|
InputProps={{ |
||||||
|
startAdornment: ( |
||||||
|
<InputAdornment position="start"> |
||||||
|
<Business /> |
||||||
|
</InputAdornment> |
||||||
|
), |
||||||
|
}} |
||||||
|
/> |
||||||
|
</Grid> |
||||||
|
<Grid size={{ xs: 12, sm: 6 }}> |
||||||
|
<TextField |
||||||
|
fullWidth |
||||||
|
label="Position" |
||||||
|
value={localProfile.position || ''} |
||||||
|
onChange={(e) => handleChange('position', e.target.value)} |
||||||
|
InputProps={{ |
||||||
|
startAdornment: ( |
||||||
|
<InputAdornment position="start"> |
||||||
|
<Work /> |
||||||
|
</InputAdornment> |
||||||
|
), |
||||||
|
}} |
||||||
|
/> |
||||||
|
</Grid> |
||||||
|
<Grid size={{ xs: 12 }}> |
||||||
|
<TextField |
||||||
|
fullWidth |
||||||
|
label="Bio" |
||||||
|
multiline |
||||||
|
rows={3} |
||||||
|
value={localProfile.bio || ''} |
||||||
|
onChange={(e) => handleChange('bio', e.target.value)} |
||||||
|
placeholder="Tell us a bit about yourself..." |
||||||
|
helperText="Optional: A brief description about yourself" |
||||||
|
/> |
||||||
|
</Grid> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<Alert severity="info" sx={{ mt: 3 }}> |
||||||
|
Don't worry, you can always update this information later in your profile settings. |
||||||
|
</Alert> |
||||||
|
</Box> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default BasicInfoStep; |
@ -0,0 +1,277 @@ |
|||||||
|
import { useState } from 'react'; |
||||||
|
import { |
||||||
|
Box, |
||||||
|
Typography, |
||||||
|
Card, |
||||||
|
CardContent, |
||||||
|
CardActions, |
||||||
|
Button, |
||||||
|
Switch, |
||||||
|
FormControlLabel, |
||||||
|
Grid, |
||||||
|
Alert, |
||||||
|
Chip, |
||||||
|
Dialog, |
||||||
|
DialogTitle, |
||||||
|
DialogContent, |
||||||
|
DialogActions, |
||||||
|
CircularProgress, |
||||||
|
Avatar |
||||||
|
} from '@mui/material'; |
||||||
|
import { |
||||||
|
LinkedIn, |
||||||
|
Contacts, |
||||||
|
Google, |
||||||
|
Apple, |
||||||
|
CheckCircle, |
||||||
|
Cloud, |
||||||
|
Security |
||||||
|
} from '@mui/icons-material'; |
||||||
|
import { useOnboarding } from '../../context/OnboardingContext'; |
||||||
|
|
||||||
|
const ConnectAccountsStep = () => { |
||||||
|
const { state, connectAccount, disconnectAccount } = useOnboarding(); |
||||||
|
const [connectingAccount, setConnectingAccount] = useState<string | null>(null); |
||||||
|
const [connectionDialog, setConnectionDialog] = useState<{ |
||||||
|
open: boolean; |
||||||
|
account: any; |
||||||
|
action: 'connect' | 'disconnect'; |
||||||
|
}>({ |
||||||
|
open: false, |
||||||
|
account: null, |
||||||
|
action: 'connect', |
||||||
|
}); |
||||||
|
|
||||||
|
const getAccountIcon = (type: string) => { |
||||||
|
switch (type) { |
||||||
|
case 'linkedin': |
||||||
|
return <LinkedIn sx={{ color: '#0077b5', fontSize: 40 }} />; |
||||||
|
case 'contacts': |
||||||
|
return <Contacts sx={{ color: '#4caf50', fontSize: 40 }} />; |
||||||
|
case 'google': |
||||||
|
return <Google sx={{ color: '#db4437', fontSize: 40 }} />; |
||||||
|
case 'apple': |
||||||
|
return <Apple sx={{ color: '#000000', fontSize: 40 }} />; |
||||||
|
default: |
||||||
|
return <Cloud sx={{ fontSize: 40 }} />; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const getAccountDescription = (type: string) => { |
||||||
|
switch (type) { |
||||||
|
case 'linkedin': |
||||||
|
return 'Import your LinkedIn connections and professional information'; |
||||||
|
case 'contacts': |
||||||
|
return 'Access your device contacts to find existing connections'; |
||||||
|
case 'google': |
||||||
|
return 'Connect your Google account for seamless contact management'; |
||||||
|
case 'apple': |
||||||
|
return 'Use Apple ID to sync contacts and profile information'; |
||||||
|
default: |
||||||
|
return 'Connect this account to enhance your network'; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleConnectionToggle = (account: any) => { |
||||||
|
if (account.isConnected) { |
||||||
|
setConnectionDialog({ |
||||||
|
open: true, |
||||||
|
account, |
||||||
|
action: 'disconnect', |
||||||
|
}); |
||||||
|
} else { |
||||||
|
setConnectionDialog({ |
||||||
|
open: true, |
||||||
|
account, |
||||||
|
action: 'connect', |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleConfirmConnection = async () => { |
||||||
|
const { account, action } = connectionDialog; |
||||||
|
setConnectingAccount(account.id); |
||||||
|
|
||||||
|
try { |
||||||
|
// Simulate API call
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000)); |
||||||
|
|
||||||
|
if (action === 'connect') { |
||||||
|
connectAccount(account.id); |
||||||
|
} else { |
||||||
|
disconnectAccount(account.id); |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
console.error('Connection failed:', error); |
||||||
|
} finally { |
||||||
|
setConnectingAccount(null); |
||||||
|
setConnectionDialog({ open: false, account: null, action: 'connect' }); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleCloseDialog = () => { |
||||||
|
if (!connectingAccount) { |
||||||
|
setConnectionDialog({ open: false, account: null, action: 'connect' }); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const connectedCount = state.connectedAccounts.filter(acc => acc.isConnected).length; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Box sx={{ maxWidth: '800px', mx: 'auto' }}> |
||||||
|
<Typography variant="h5" gutterBottom align="center"> |
||||||
|
Connect Your Accounts |
||||||
|
</Typography> |
||||||
|
<Typography variant="body1" color="text.secondary" align="center" sx={{ mb: 2 }}> |
||||||
|
Connect your social and professional accounts to enhance your network experience |
||||||
|
</Typography> |
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 4 }}> |
||||||
|
<Chip |
||||||
|
icon={<CheckCircle />} |
||||||
|
label={`${connectedCount} of ${state.connectedAccounts.length} accounts connected`} |
||||||
|
color={connectedCount > 0 ? 'success' : 'default'} |
||||||
|
variant="outlined" |
||||||
|
/> |
||||||
|
</Box> |
||||||
|
|
||||||
|
<Grid container spacing={3}> |
||||||
|
{state.connectedAccounts.map((account) => ( |
||||||
|
<Grid size={{ xs: 12, sm: 6 }} key={account.id}> |
||||||
|
<Card |
||||||
|
sx={{ |
||||||
|
height: '100%', |
||||||
|
position: 'relative', |
||||||
|
border: account.isConnected ? '2px solid' : '1px solid', |
||||||
|
borderColor: account.isConnected ? 'success.main' : 'divider', |
||||||
|
transition: 'all 0.3s ease-in-out', |
||||||
|
'&:hover': { |
||||||
|
boxShadow: 3, |
||||||
|
transform: 'translateY(-2px)', |
||||||
|
}, |
||||||
|
}} |
||||||
|
> |
||||||
|
{account.isConnected && ( |
||||||
|
<CheckCircle |
||||||
|
sx={{ |
||||||
|
position: 'absolute', |
||||||
|
top: 8, |
||||||
|
right: 8, |
||||||
|
color: 'success.main', |
||||||
|
fontSize: 24, |
||||||
|
}} |
||||||
|
/> |
||||||
|
)} |
||||||
|
|
||||||
|
<CardContent sx={{ textAlign: 'center', pb: 1 }}> |
||||||
|
<Box sx={{ mb: 2 }}> |
||||||
|
{getAccountIcon(account.type)} |
||||||
|
</Box> |
||||||
|
<Typography variant="h6" gutterBottom> |
||||||
|
{account.name} |
||||||
|
</Typography> |
||||||
|
<Typography variant="body2" color="text.secondary"> |
||||||
|
{getAccountDescription(account.type)} |
||||||
|
</Typography> |
||||||
|
|
||||||
|
{account.isConnected && account.connectedAt && ( |
||||||
|
<Typography variant="caption" color="success.main" sx={{ mt: 1, display: 'block' }}> |
||||||
|
Connected {new Date(account.connectedAt).toLocaleDateString()} |
||||||
|
</Typography> |
||||||
|
)} |
||||||
|
</CardContent> |
||||||
|
|
||||||
|
<CardActions sx={{ justifyContent: 'center', pb: 2 }}> |
||||||
|
<FormControlLabel |
||||||
|
control={ |
||||||
|
<Switch |
||||||
|
checked={account.isConnected} |
||||||
|
onChange={() => handleConnectionToggle(account)} |
||||||
|
disabled={connectingAccount === account.id} |
||||||
|
/> |
||||||
|
} |
||||||
|
label={account.isConnected ? 'Connected' : 'Connect'} |
||||||
|
/> |
||||||
|
</CardActions> |
||||||
|
</Card> |
||||||
|
</Grid> |
||||||
|
))} |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<Alert severity="info" sx={{ mt: 3 }}> |
||||||
|
<Typography variant="body2"> |
||||||
|
<Security sx={{ verticalAlign: 'middle', mr: 1 }} /> |
||||||
|
Your privacy is important to us. We only access the information necessary to enhance your network experience. |
||||||
|
</Typography> |
||||||
|
</Alert> |
||||||
|
|
||||||
|
<Alert severity="success" sx={{ mt: 2 }}> |
||||||
|
<Typography variant="body2"> |
||||||
|
You can skip this step and connect accounts later from your profile settings. |
||||||
|
</Typography> |
||||||
|
</Alert> |
||||||
|
|
||||||
|
<Dialog |
||||||
|
open={connectionDialog.open} |
||||||
|
onClose={handleCloseDialog} |
||||||
|
maxWidth="sm" |
||||||
|
fullWidth |
||||||
|
> |
||||||
|
<DialogTitle> |
||||||
|
{connectionDialog.action === 'connect' ? 'Connect' : 'Disconnect'} {connectionDialog.account?.name} |
||||||
|
</DialogTitle> |
||||||
|
<DialogContent> |
||||||
|
{connectingAccount ? ( |
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', py: 3 }}> |
||||||
|
<CircularProgress size={60} sx={{ mb: 2 }} /> |
||||||
|
<Typography variant="body1"> |
||||||
|
{connectionDialog.action === 'connect' ? 'Connecting' : 'Disconnecting'} your {connectionDialog.account?.name} account... |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
) : ( |
||||||
|
<Box sx={{ textAlign: 'center', py: 2 }}> |
||||||
|
<Avatar sx={{ mx: 'auto', mb: 2, width: 64, height: 64 }}> |
||||||
|
{connectionDialog.account && getAccountIcon(connectionDialog.account.type)} |
||||||
|
</Avatar> |
||||||
|
<Typography variant="body1"> |
||||||
|
{connectionDialog.action === 'connect' ? ( |
||||||
|
<> |
||||||
|
Do you want to connect your {connectionDialog.account?.name} account? |
||||||
|
<br /> |
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}> |
||||||
|
{connectionDialog.account && getAccountDescription(connectionDialog.account.type)} |
||||||
|
</Typography> |
||||||
|
</> |
||||||
|
) : ( |
||||||
|
<> |
||||||
|
Are you sure you want to disconnect your {connectionDialog.account?.name} account? |
||||||
|
<br /> |
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}> |
||||||
|
You can reconnect it at any time from your profile settings. |
||||||
|
</Typography> |
||||||
|
</> |
||||||
|
)} |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
)} |
||||||
|
</DialogContent> |
||||||
|
<DialogActions> |
||||||
|
{!connectingAccount && ( |
||||||
|
<> |
||||||
|
<Button onClick={handleCloseDialog}>Cancel</Button> |
||||||
|
<Button |
||||||
|
onClick={handleConfirmConnection} |
||||||
|
variant="contained" |
||||||
|
color={connectionDialog.action === 'connect' ? 'primary' : 'error'} |
||||||
|
> |
||||||
|
{connectionDialog.action === 'connect' ? 'Connect' : 'Disconnect'} |
||||||
|
</Button> |
||||||
|
</> |
||||||
|
)} |
||||||
|
</DialogActions> |
||||||
|
</Dialog> |
||||||
|
</Box> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default ConnectAccountsStep; |
@ -0,0 +1,153 @@ |
|||||||
|
import { createContext, useContext, useReducer } from 'react'; |
||||||
|
import type { ReactNode } from 'react'; |
||||||
|
import type { OnboardingState, OnboardingContextType, UserProfile } from '../types/onboarding'; |
||||||
|
|
||||||
|
const initialState: OnboardingState = { |
||||||
|
currentStep: 0, |
||||||
|
totalSteps: 2, |
||||||
|
userProfile: {}, |
||||||
|
connectedAccounts: [ |
||||||
|
{ |
||||||
|
id: 'linkedin', |
||||||
|
type: 'linkedin', |
||||||
|
name: 'LinkedIn', |
||||||
|
isConnected: false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 'contacts', |
||||||
|
type: 'contacts', |
||||||
|
name: 'Contacts', |
||||||
|
isConnected: false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 'google', |
||||||
|
type: 'google', |
||||||
|
name: 'Google', |
||||||
|
isConnected: false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 'apple', |
||||||
|
type: 'apple', |
||||||
|
name: 'Apple', |
||||||
|
isConnected: false, |
||||||
|
}, |
||||||
|
], |
||||||
|
isComplete: false, |
||||||
|
}; |
||||||
|
|
||||||
|
type OnboardingAction = |
||||||
|
| { type: 'UPDATE_PROFILE'; payload: Partial<UserProfile> } |
||||||
|
| { type: 'CONNECT_ACCOUNT'; payload: string } |
||||||
|
| { type: 'DISCONNECT_ACCOUNT'; payload: string } |
||||||
|
| { type: 'NEXT_STEP' } |
||||||
|
| { type: 'PREV_STEP' } |
||||||
|
| { type: 'COMPLETE_ONBOARDING' } |
||||||
|
| { type: 'RESET' }; |
||||||
|
|
||||||
|
const onboardingReducer = (state: OnboardingState, action: OnboardingAction): OnboardingState => { |
||||||
|
switch (action.type) { |
||||||
|
case 'UPDATE_PROFILE': |
||||||
|
return { |
||||||
|
...state, |
||||||
|
userProfile: { ...state.userProfile, ...action.payload }, |
||||||
|
}; |
||||||
|
|
||||||
|
case 'CONNECT_ACCOUNT': |
||||||
|
return { |
||||||
|
...state, |
||||||
|
connectedAccounts: state.connectedAccounts.map(account => |
||||||
|
account.id === action.payload |
||||||
|
? { ...account, isConnected: true, connectedAt: new Date() } |
||||||
|
: account |
||||||
|
), |
||||||
|
}; |
||||||
|
|
||||||
|
case 'DISCONNECT_ACCOUNT': |
||||||
|
return { |
||||||
|
...state, |
||||||
|
connectedAccounts: state.connectedAccounts.map(account => |
||||||
|
account.id === action.payload |
||||||
|
? { ...account, isConnected: false, connectedAt: undefined } |
||||||
|
: account |
||||||
|
), |
||||||
|
}; |
||||||
|
|
||||||
|
case 'NEXT_STEP': |
||||||
|
return { |
||||||
|
...state, |
||||||
|
currentStep: Math.min(state.currentStep + 1, state.totalSteps - 1), |
||||||
|
}; |
||||||
|
|
||||||
|
case 'PREV_STEP': |
||||||
|
return { |
||||||
|
...state, |
||||||
|
currentStep: Math.max(state.currentStep - 1, 0), |
||||||
|
}; |
||||||
|
|
||||||
|
case 'COMPLETE_ONBOARDING': |
||||||
|
return { |
||||||
|
...state, |
||||||
|
isComplete: true, |
||||||
|
}; |
||||||
|
|
||||||
|
case 'RESET': |
||||||
|
return initialState; |
||||||
|
|
||||||
|
default: |
||||||
|
return state; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const OnboardingContext = createContext<OnboardingContextType | undefined>(undefined); |
||||||
|
|
||||||
|
export const OnboardingProvider = ({ children }: { children: ReactNode }) => { |
||||||
|
const [state, dispatch] = useReducer(onboardingReducer, initialState); |
||||||
|
|
||||||
|
const updateProfile = (profile: Partial<UserProfile>) => { |
||||||
|
dispatch({ type: 'UPDATE_PROFILE', payload: profile }); |
||||||
|
}; |
||||||
|
|
||||||
|
const connectAccount = (accountId: string) => { |
||||||
|
dispatch({ type: 'CONNECT_ACCOUNT', payload: accountId }); |
||||||
|
}; |
||||||
|
|
||||||
|
const disconnectAccount = (accountId: string) => { |
||||||
|
dispatch({ type: 'DISCONNECT_ACCOUNT', payload: accountId }); |
||||||
|
}; |
||||||
|
|
||||||
|
const nextStep = () => { |
||||||
|
dispatch({ type: 'NEXT_STEP' }); |
||||||
|
}; |
||||||
|
|
||||||
|
const prevStep = () => { |
||||||
|
dispatch({ type: 'PREV_STEP' }); |
||||||
|
}; |
||||||
|
|
||||||
|
const completeOnboarding = () => { |
||||||
|
dispatch({ type: 'COMPLETE_ONBOARDING' }); |
||||||
|
}; |
||||||
|
|
||||||
|
const value: OnboardingContextType = { |
||||||
|
state, |
||||||
|
updateProfile, |
||||||
|
connectAccount, |
||||||
|
disconnectAccount, |
||||||
|
nextStep, |
||||||
|
prevStep, |
||||||
|
completeOnboarding, |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<OnboardingContext.Provider value={value}> |
||||||
|
{children} |
||||||
|
</OnboardingContext.Provider> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export const useOnboarding = () => { |
||||||
|
const context = useContext(OnboardingContext); |
||||||
|
if (context === undefined) { |
||||||
|
throw new Error('useOnboarding must be used within an OnboardingProvider'); |
||||||
|
} |
||||||
|
return context; |
||||||
|
}; |
@ -0,0 +1,68 @@ |
|||||||
|
:root { |
||||||
|
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; |
||||||
|
line-height: 1.5; |
||||||
|
font-weight: 400; |
||||||
|
|
||||||
|
color-scheme: light dark; |
||||||
|
color: rgba(255, 255, 255, 0.87); |
||||||
|
background-color: #242424; |
||||||
|
|
||||||
|
font-synthesis: none; |
||||||
|
text-rendering: optimizeLegibility; |
||||||
|
-webkit-font-smoothing: antialiased; |
||||||
|
-moz-osx-font-smoothing: grayscale; |
||||||
|
} |
||||||
|
|
||||||
|
a { |
||||||
|
font-weight: 500; |
||||||
|
color: #646cff; |
||||||
|
text-decoration: inherit; |
||||||
|
} |
||||||
|
a:hover { |
||||||
|
color: #535bf2; |
||||||
|
} |
||||||
|
|
||||||
|
body { |
||||||
|
margin: 0; |
||||||
|
display: flex; |
||||||
|
place-items: center; |
||||||
|
min-width: 320px; |
||||||
|
min-height: 100vh; |
||||||
|
} |
||||||
|
|
||||||
|
h1 { |
||||||
|
font-size: 3.2em; |
||||||
|
line-height: 1.1; |
||||||
|
} |
||||||
|
|
||||||
|
button { |
||||||
|
border-radius: 8px; |
||||||
|
border: 1px solid transparent; |
||||||
|
padding: 0.6em 1.2em; |
||||||
|
font-size: 1em; |
||||||
|
font-weight: 500; |
||||||
|
font-family: inherit; |
||||||
|
background-color: #1a1a1a; |
||||||
|
cursor: pointer; |
||||||
|
transition: border-color 0.25s; |
||||||
|
} |
||||||
|
button:hover { |
||||||
|
border-color: #646cff; |
||||||
|
} |
||||||
|
button:focus, |
||||||
|
button:focus-visible { |
||||||
|
outline: 4px auto -webkit-focus-ring-color; |
||||||
|
} |
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) { |
||||||
|
:root { |
||||||
|
color: #213547; |
||||||
|
background-color: #ffffff; |
||||||
|
} |
||||||
|
a:hover { |
||||||
|
color: #747bff; |
||||||
|
} |
||||||
|
button { |
||||||
|
background-color: #f9f9f9; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
import { StrictMode } from 'react' |
||||||
|
import { createRoot } from 'react-dom/client' |
||||||
|
import './index.css' |
||||||
|
import App from './App.tsx' |
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render( |
||||||
|
<StrictMode> |
||||||
|
<App /> |
||||||
|
</StrictMode>, |
||||||
|
) |
@ -0,0 +1,240 @@ |
|||||||
|
import { useState, useEffect } from 'react'; |
||||||
|
import { useNavigate } from 'react-router-dom'; |
||||||
|
import { |
||||||
|
Container, |
||||||
|
Typography, |
||||||
|
Box, |
||||||
|
Tabs, |
||||||
|
Tab, |
||||||
|
List, |
||||||
|
ListItem, |
||||||
|
ListItemAvatar, |
||||||
|
ListItemText, |
||||||
|
Avatar, |
||||||
|
Chip, |
||||||
|
Paper, |
||||||
|
Divider, |
||||||
|
TextField, |
||||||
|
InputAdornment, |
||||||
|
Button, |
||||||
|
Tooltip |
||||||
|
} from '@mui/material'; |
||||||
|
import { |
||||||
|
List as ListIcon, |
||||||
|
AccountTree, |
||||||
|
ScatterPlot, |
||||||
|
Search, |
||||||
|
Add, |
||||||
|
LinkedIn, |
||||||
|
Phone, |
||||||
|
Email, |
||||||
|
QrCode |
||||||
|
} from '@mui/icons-material'; |
||||||
|
import { dataService } from '../services/dataService'; |
||||||
|
import type { Contact } from '../types/contact'; |
||||||
|
|
||||||
|
const ContactListPage = () => { |
||||||
|
const [contacts, setContacts] = useState<Contact[]>([]); |
||||||
|
const [filteredContacts, setFilteredContacts] = useState<Contact[]>([]); |
||||||
|
const [searchQuery, setSearchQuery] = useState(''); |
||||||
|
const [tabValue, setTabValue] = useState(0); |
||||||
|
const [isLoading, setIsLoading] = useState(true); |
||||||
|
const navigate = useNavigate(); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const loadContacts = async () => { |
||||||
|
setIsLoading(true); |
||||||
|
try { |
||||||
|
const contactsData = await dataService.getContacts(); |
||||||
|
setContacts(contactsData); |
||||||
|
setFilteredContacts(contactsData); |
||||||
|
} catch (error) { |
||||||
|
console.error('Failed to load contacts:', error); |
||||||
|
} finally { |
||||||
|
setIsLoading(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
loadContacts(); |
||||||
|
}, []); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const filtered = contacts.filter(contact => |
||||||
|
contact.name.toLowerCase().includes(searchQuery.toLowerCase()) || |
||||||
|
contact.email.toLowerCase().includes(searchQuery.toLowerCase()) || |
||||||
|
contact.company?.toLowerCase().includes(searchQuery.toLowerCase()) || |
||||||
|
contact.position?.toLowerCase().includes(searchQuery.toLowerCase()) |
||||||
|
); |
||||||
|
setFilteredContacts(filtered); |
||||||
|
}, [searchQuery, contacts]); |
||||||
|
|
||||||
|
const handleContactClick = (contactId: string) => { |
||||||
|
navigate(`/contacts/${contactId}`); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { |
||||||
|
setTabValue(newValue); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleAddContact = () => { |
||||||
|
navigate('/'); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleInvite = () => { |
||||||
|
navigate('/invite'); |
||||||
|
}; |
||||||
|
|
||||||
|
const getSourceIcon = (source: string) => { |
||||||
|
switch (source) { |
||||||
|
case 'linkedin': |
||||||
|
return <LinkedIn sx={{ fontSize: 16, color: '#0077b5' }} />; |
||||||
|
case 'contacts': |
||||||
|
return <Phone sx={{ fontSize: 16, color: '#4caf50' }} />; |
||||||
|
default: |
||||||
|
return <Email sx={{ fontSize: 16 }} />; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const formatDate = (date: Date) => { |
||||||
|
return new Intl.DateTimeFormat('en-US', { |
||||||
|
year: 'numeric', |
||||||
|
month: 'short', |
||||||
|
day: 'numeric' |
||||||
|
}).format(date); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Container maxWidth="lg" sx={{ py: 3 }}> |
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}> |
||||||
|
<Typography variant="h4" component="h1"> |
||||||
|
Contacts ({filteredContacts.length}) |
||||||
|
</Typography> |
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}> |
||||||
|
<Button |
||||||
|
variant="outlined" |
||||||
|
startIcon={<QrCode />} |
||||||
|
onClick={handleInvite} |
||||||
|
> |
||||||
|
Invite |
||||||
|
</Button> |
||||||
|
<Button |
||||||
|
variant="contained" |
||||||
|
startIcon={<Add />} |
||||||
|
onClick={handleAddContact} |
||||||
|
> |
||||||
|
Add Contacts |
||||||
|
</Button> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
|
||||||
|
<Paper sx={{ mb: 3 }}> |
||||||
|
<Tabs |
||||||
|
value={tabValue} |
||||||
|
onChange={handleTabChange} |
||||||
|
aria-label="contact view tabs" |
||||||
|
sx={{ borderBottom: 1, borderColor: 'divider' }} |
||||||
|
> |
||||||
|
<Tab icon={<ListIcon />} label="List View" /> |
||||||
|
<Tooltip title="Coming soon"> |
||||||
|
<span> |
||||||
|
<Tab icon={<AccountTree />} label="Network View" disabled /> |
||||||
|
</span> |
||||||
|
</Tooltip> |
||||||
|
<Tooltip title="Coming soon"> |
||||||
|
<span> |
||||||
|
<Tab icon={<ScatterPlot />} label="Graph View" disabled /> |
||||||
|
</span> |
||||||
|
</Tooltip> |
||||||
|
</Tabs> |
||||||
|
|
||||||
|
<Box sx={{ p: 2 }}> |
||||||
|
<TextField |
||||||
|
fullWidth |
||||||
|
placeholder="Search contacts..." |
||||||
|
value={searchQuery} |
||||||
|
onChange={(e) => setSearchQuery(e.target.value)} |
||||||
|
InputProps={{ |
||||||
|
startAdornment: ( |
||||||
|
<InputAdornment position="start"> |
||||||
|
<Search /> |
||||||
|
</InputAdornment> |
||||||
|
), |
||||||
|
}} |
||||||
|
sx={{ mb: 2 }} |
||||||
|
/> |
||||||
|
|
||||||
|
{isLoading ? ( |
||||||
|
<Box sx={{ textAlign: 'center', py: 4 }}> |
||||||
|
<Typography variant="body1" color="text.secondary"> |
||||||
|
Loading contacts... |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
) : filteredContacts.length === 0 ? ( |
||||||
|
<Box sx={{ textAlign: 'center', py: 4 }}> |
||||||
|
<Typography variant="body1" color="text.secondary"> |
||||||
|
{searchQuery ? 'No contacts found matching your search.' : 'No contacts yet. Import some contacts to get started!'} |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
) : ( |
||||||
|
<List> |
||||||
|
{filteredContacts.map((contact, index) => ( |
||||||
|
<Box key={contact.id}> |
||||||
|
<ListItem |
||||||
|
onClick={() => handleContactClick(contact.id)} |
||||||
|
sx={{ |
||||||
|
cursor: 'pointer', |
||||||
|
borderRadius: 1, |
||||||
|
'&:hover': { |
||||||
|
backgroundColor: 'action.hover', |
||||||
|
}, |
||||||
|
}} |
||||||
|
> |
||||||
|
<ListItemAvatar> |
||||||
|
<Avatar |
||||||
|
src={contact.profileImage} |
||||||
|
alt={contact.name} |
||||||
|
sx={{ width: 56, height: 56 }} |
||||||
|
> |
||||||
|
{contact.name.charAt(0)} |
||||||
|
</Avatar> |
||||||
|
</ListItemAvatar> |
||||||
|
<ListItemText |
||||||
|
primary={ |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> |
||||||
|
<Typography variant="h6" component="span"> |
||||||
|
{contact.name} |
||||||
|
</Typography> |
||||||
|
{getSourceIcon(contact.source)} |
||||||
|
</Box> |
||||||
|
} |
||||||
|
secondary={ |
||||||
|
<Box sx={{ mt: 1 }}> |
||||||
|
<Typography variant="body2" color="text.secondary"> |
||||||
|
{contact.position} {contact.company && `at ${contact.company}`} |
||||||
|
</Typography> |
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}> |
||||||
|
{contact.email} |
||||||
|
</Typography> |
||||||
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}> |
||||||
|
{contact.tags?.map((tag) => ( |
||||||
|
<Chip key={tag} label={tag} size="small" variant="outlined" /> |
||||||
|
))} |
||||||
|
</Box> |
||||||
|
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}> |
||||||
|
Added {formatDate(contact.createdAt)} |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
} |
||||||
|
/> |
||||||
|
</ListItem> |
||||||
|
{index < filteredContacts.length - 1 && <Divider variant="inset" component="li" />} |
||||||
|
</Box> |
||||||
|
))} |
||||||
|
</List> |
||||||
|
)} |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
</Container> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default ContactListPage; |
@ -0,0 +1,337 @@ |
|||||||
|
import { useState, useEffect } from 'react'; |
||||||
|
import { useParams, useNavigate } from 'react-router-dom'; |
||||||
|
import { |
||||||
|
Container, |
||||||
|
Typography, |
||||||
|
Box, |
||||||
|
Paper, |
||||||
|
Avatar, |
||||||
|
Button, |
||||||
|
Chip, |
||||||
|
Divider, |
||||||
|
Grid, |
||||||
|
IconButton, |
||||||
|
Card, |
||||||
|
CardContent, |
||||||
|
Alert, |
||||||
|
Skeleton |
||||||
|
} from '@mui/material'; |
||||||
|
import { |
||||||
|
ArrowBack, |
||||||
|
Email, |
||||||
|
Phone, |
||||||
|
Business, |
||||||
|
LinkedIn, |
||||||
|
Edit, |
||||||
|
Share, |
||||||
|
Delete, |
||||||
|
Person, |
||||||
|
Schedule, |
||||||
|
Source |
||||||
|
} from '@mui/icons-material'; |
||||||
|
import { dataService } from '../services/dataService'; |
||||||
|
import type { Contact } from '../types/contact'; |
||||||
|
|
||||||
|
const ContactViewPage = () => { |
||||||
|
const { id } = useParams<{ id: string }>(); |
||||||
|
const [contact, setContact] = useState<Contact | null>(null); |
||||||
|
const [isLoading, setIsLoading] = useState(true); |
||||||
|
const [error, setError] = useState<string | null>(null); |
||||||
|
const navigate = useNavigate(); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const loadContact = async () => { |
||||||
|
if (!id) { |
||||||
|
setError('Contact ID is required'); |
||||||
|
setIsLoading(false); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
setIsLoading(true); |
||||||
|
try { |
||||||
|
const contactData = await dataService.getContact(id); |
||||||
|
if (contactData) { |
||||||
|
setContact(contactData); |
||||||
|
} else { |
||||||
|
setError('Contact not found'); |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
console.error('Failed to load contact:', error); |
||||||
|
setError('Failed to load contact'); |
||||||
|
} finally { |
||||||
|
setIsLoading(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
loadContact(); |
||||||
|
}, [id]); |
||||||
|
|
||||||
|
const handleBack = () => { |
||||||
|
navigate('/contacts'); |
||||||
|
}; |
||||||
|
|
||||||
|
const formatDate = (date: Date) => { |
||||||
|
return new Intl.DateTimeFormat('en-US', { |
||||||
|
year: 'numeric', |
||||||
|
month: 'long', |
||||||
|
day: 'numeric', |
||||||
|
hour: '2-digit', |
||||||
|
minute: '2-digit' |
||||||
|
}).format(date); |
||||||
|
}; |
||||||
|
|
||||||
|
const getSourceDetails = (source: string) => { |
||||||
|
switch (source) { |
||||||
|
case 'linkedin': |
||||||
|
return { |
||||||
|
name: 'LinkedIn', |
||||||
|
icon: <LinkedIn sx={{ color: '#0077b5' }} />, |
||||||
|
color: '#0077b5' |
||||||
|
}; |
||||||
|
case 'contacts': |
||||||
|
return { |
||||||
|
name: 'Contacts', |
||||||
|
icon: <Person sx={{ color: '#4caf50' }} />, |
||||||
|
color: '#4caf50' |
||||||
|
}; |
||||||
|
default: |
||||||
|
return { |
||||||
|
name: 'Unknown', |
||||||
|
icon: <Source />, |
||||||
|
color: 'grey' |
||||||
|
}; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
if (isLoading) { |
||||||
|
return ( |
||||||
|
<Container maxWidth="md" sx={{ py: 3 }}> |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}> |
||||||
|
<Skeleton variant="circular" width={40} height={40} /> |
||||||
|
<Skeleton variant="text" width={200} height={40} sx={{ ml: 2 }} /> |
||||||
|
</Box> |
||||||
|
<Paper sx={{ p: 3 }}> |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}> |
||||||
|
<Skeleton variant="circular" width={120} height={120} /> |
||||||
|
<Box sx={{ ml: 3, flex: 1 }}> |
||||||
|
<Skeleton variant="text" width={200} height={40} /> |
||||||
|
<Skeleton variant="text" width={300} height={24} /> |
||||||
|
<Skeleton variant="text" width={250} height={24} /> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
</Container> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if (error || !contact) { |
||||||
|
return ( |
||||||
|
<Container maxWidth="md" sx={{ py: 3 }}> |
||||||
|
<Button |
||||||
|
startIcon={<ArrowBack />} |
||||||
|
onClick={handleBack} |
||||||
|
sx={{ mb: 3 }} |
||||||
|
> |
||||||
|
Back to Contacts |
||||||
|
</Button> |
||||||
|
<Alert severity="error"> |
||||||
|
{error || 'Contact not found'} |
||||||
|
</Alert> |
||||||
|
</Container> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
const sourceDetails = getSourceDetails(contact.source); |
||||||
|
|
||||||
|
return ( |
||||||
|
<Container maxWidth="md" sx={{ py: 3 }}> |
||||||
|
<Button |
||||||
|
startIcon={<ArrowBack />} |
||||||
|
onClick={handleBack} |
||||||
|
sx={{ mb: 3 }} |
||||||
|
> |
||||||
|
Back to Contacts |
||||||
|
</Button> |
||||||
|
|
||||||
|
<Paper sx={{ p: 3, mb: 3 }}> |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'flex-start', mb: 3 }}> |
||||||
|
<Avatar |
||||||
|
src={contact.profileImage} |
||||||
|
alt={contact.name} |
||||||
|
sx={{ width: 120, height: 120, mr: 3 }} |
||||||
|
> |
||||||
|
{contact.name.charAt(0)} |
||||||
|
</Avatar> |
||||||
|
|
||||||
|
<Box sx={{ flex: 1 }}> |
||||||
|
<Typography variant="h4" component="h1" gutterBottom> |
||||||
|
{contact.name} |
||||||
|
</Typography> |
||||||
|
|
||||||
|
{contact.position && ( |
||||||
|
<Typography variant="h6" color="text.secondary" gutterBottom> |
||||||
|
{contact.position} |
||||||
|
{contact.company && ` at ${contact.company}`} |
||||||
|
</Typography> |
||||||
|
)} |
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}> |
||||||
|
<Chip |
||||||
|
icon={sourceDetails.icon} |
||||||
|
label={`From ${sourceDetails.name}`} |
||||||
|
variant="outlined" |
||||||
|
sx={{ color: sourceDetails.color, borderColor: sourceDetails.color }} |
||||||
|
/> |
||||||
|
</Box> |
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', mb: 2 }}> |
||||||
|
{contact.tags?.map((tag) => ( |
||||||
|
<Chip key={tag} label={tag} size="small" /> |
||||||
|
))} |
||||||
|
</Box> |
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}> |
||||||
|
<Button variant="outlined" startIcon={<Edit />} size="small"> |
||||||
|
Edit |
||||||
|
</Button> |
||||||
|
<Button variant="outlined" startIcon={<Share />} size="small"> |
||||||
|
Share |
||||||
|
</Button> |
||||||
|
<IconButton color="error" size="small"> |
||||||
|
<Delete /> |
||||||
|
</IconButton> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
|
||||||
|
<Divider sx={{ my: 3 }} /> |
||||||
|
|
||||||
|
<Grid container spacing={3}> |
||||||
|
<Grid size={{ xs: 12, md: 6 }}> |
||||||
|
<Card variant="outlined"> |
||||||
|
<CardContent> |
||||||
|
<Typography variant="h6" gutterBottom> |
||||||
|
Contact Information |
||||||
|
</Typography> |
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> |
||||||
|
<Email sx={{ mr: 2, color: 'text.secondary' }} /> |
||||||
|
<Box> |
||||||
|
<Typography variant="body2" color="text.secondary"> |
||||||
|
Email |
||||||
|
</Typography> |
||||||
|
<Typography variant="body1"> |
||||||
|
{contact.email} |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
|
||||||
|
{contact.phone && ( |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> |
||||||
|
<Phone sx={{ mr: 2, color: 'text.secondary' }} /> |
||||||
|
<Box> |
||||||
|
<Typography variant="body2" color="text.secondary"> |
||||||
|
Phone |
||||||
|
</Typography> |
||||||
|
<Typography variant="body1"> |
||||||
|
{contact.phone} |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
)} |
||||||
|
|
||||||
|
{contact.company && ( |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> |
||||||
|
<Business sx={{ mr: 2, color: 'text.secondary' }} /> |
||||||
|
<Box> |
||||||
|
<Typography variant="body2" color="text.secondary"> |
||||||
|
Company |
||||||
|
</Typography> |
||||||
|
<Typography variant="body1"> |
||||||
|
{contact.company} |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
)} |
||||||
|
|
||||||
|
{contact.linkedinUrl && ( |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> |
||||||
|
<LinkedIn sx={{ mr: 2, color: '#0077b5' }} /> |
||||||
|
<Box> |
||||||
|
<Typography variant="body2" color="text.secondary"> |
||||||
|
LinkedIn |
||||||
|
</Typography> |
||||||
|
<Typography |
||||||
|
variant="body1" |
||||||
|
component="a" |
||||||
|
href={contact.linkedinUrl} |
||||||
|
target="_blank" |
||||||
|
rel="noopener noreferrer" |
||||||
|
sx={{ |
||||||
|
color: '#0077b5', |
||||||
|
textDecoration: 'none', |
||||||
|
'&:hover': { |
||||||
|
textDecoration: 'underline', |
||||||
|
}, |
||||||
|
}} |
||||||
|
> |
||||||
|
View Profile |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
)} |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}> |
||||||
|
<Card variant="outlined"> |
||||||
|
<CardContent> |
||||||
|
<Typography variant="h6" gutterBottom> |
||||||
|
Additional Information |
||||||
|
</Typography> |
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> |
||||||
|
<Schedule sx={{ mr: 2, color: 'text.secondary' }} /> |
||||||
|
<Box> |
||||||
|
<Typography variant="body2" color="text.secondary"> |
||||||
|
Added |
||||||
|
</Typography> |
||||||
|
<Typography variant="body1"> |
||||||
|
{formatDate(contact.createdAt)} |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> |
||||||
|
<Schedule sx={{ mr: 2, color: 'text.secondary' }} /> |
||||||
|
<Box> |
||||||
|
<Typography variant="body2" color="text.secondary"> |
||||||
|
Last Updated |
||||||
|
</Typography> |
||||||
|
<Typography variant="body1"> |
||||||
|
{formatDate(contact.updatedAt)} |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
|
||||||
|
{contact.notes && ( |
||||||
|
<Box sx={{ mt: 2 }}> |
||||||
|
<Typography variant="body2" color="text.secondary" gutterBottom> |
||||||
|
Notes |
||||||
|
</Typography> |
||||||
|
<Typography variant="body1"> |
||||||
|
{contact.notes} |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
)} |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</Grid> |
||||||
|
</Grid> |
||||||
|
</Paper> |
||||||
|
</Container> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default ContactViewPage; |
@ -0,0 +1,189 @@ |
|||||||
|
import { useState, useEffect } from 'react'; |
||||||
|
import { useNavigate } from 'react-router-dom'; |
||||||
|
import { |
||||||
|
Container, |
||||||
|
Typography, |
||||||
|
Grid, |
||||||
|
Card, |
||||||
|
CardContent, |
||||||
|
CardActions, |
||||||
|
Button, |
||||||
|
Dialog, |
||||||
|
DialogTitle, |
||||||
|
DialogContent, |
||||||
|
DialogActions, |
||||||
|
CircularProgress, |
||||||
|
Box, |
||||||
|
Alert, |
||||||
|
} from '@mui/material'; |
||||||
|
import { LinkedIn, Contacts, CloudDownload } from '@mui/icons-material'; |
||||||
|
import { dataService } from '../services/dataService'; |
||||||
|
import type { ImportSource } from '../types/contact'; |
||||||
|
|
||||||
|
const ImportPage = () => { |
||||||
|
const [importSources, setImportSources] = useState<ImportSource[]>([]); |
||||||
|
const [selectedSource, setSelectedSource] = useState<ImportSource | null>(null); |
||||||
|
const [isDialogOpen, setIsDialogOpen] = useState(false); |
||||||
|
const [isLoading, setIsLoading] = useState(false); |
||||||
|
const [importComplete, setImportComplete] = useState(false); |
||||||
|
const [importedCount, setImportedCount] = useState(0); |
||||||
|
const navigate = useNavigate(); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const loadImportSources = async () => { |
||||||
|
const sources = await dataService.getImportSources(); |
||||||
|
setImportSources(sources); |
||||||
|
}; |
||||||
|
loadImportSources(); |
||||||
|
}, []); |
||||||
|
|
||||||
|
const handleImportClick = (source: ImportSource) => { |
||||||
|
setSelectedSource(source); |
||||||
|
setIsDialogOpen(true); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleConfirmImport = async () => { |
||||||
|
if (!selectedSource) return; |
||||||
|
|
||||||
|
setIsLoading(true); |
||||||
|
setImportComplete(false); |
||||||
|
|
||||||
|
try { |
||||||
|
const importedContacts = await dataService.importFromSource(selectedSource.id); |
||||||
|
setImportedCount(importedContacts.length); |
||||||
|
setImportComplete(true); |
||||||
|
setIsLoading(false); |
||||||
|
|
||||||
|
setTimeout(() => { |
||||||
|
setIsDialogOpen(false); |
||||||
|
navigate('/contacts'); |
||||||
|
}, 2000); |
||||||
|
} catch (error) { |
||||||
|
console.error('Import failed:', error); |
||||||
|
setIsLoading(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleCloseDialog = () => { |
||||||
|
if (!isLoading) { |
||||||
|
setIsDialogOpen(false); |
||||||
|
setSelectedSource(null); |
||||||
|
setImportComplete(false); |
||||||
|
setImportedCount(0); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const getSourceIcon = (iconName: string) => { |
||||||
|
switch (iconName) { |
||||||
|
case 'LinkedInIcon': |
||||||
|
return <LinkedIn sx={{ fontSize: 40, color: '#0077b5' }} />; |
||||||
|
case 'ContactsIcon': |
||||||
|
return <Contacts sx={{ fontSize: 40, color: '#4caf50' }} />; |
||||||
|
default: |
||||||
|
return <CloudDownload sx={{ fontSize: 40 }} />; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Container maxWidth="md" sx={{ py: 4 }}> |
||||||
|
<Typography variant="h4" component="h1" gutterBottom align="center"> |
||||||
|
Import Your Contacts |
||||||
|
</Typography> |
||||||
|
<Typography variant="body1" align="center" sx={{ mb: 4, color: 'text.secondary' }}> |
||||||
|
Choose a source to import your contacts from |
||||||
|
</Typography> |
||||||
|
|
||||||
|
<Grid container spacing={3}> |
||||||
|
{importSources.map((source) => ( |
||||||
|
<Grid size={{ xs: 12, md: 6 }} key={source.id}> |
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column', |
||||||
|
transition: 'all 0.3s ease-in-out', |
||||||
|
'&:hover': { |
||||||
|
transform: 'translateY(-4px)', |
||||||
|
boxShadow: 3, |
||||||
|
} |
||||||
|
}} |
||||||
|
> |
||||||
|
<CardContent sx={{ flexGrow: 1, textAlign: 'center' }}> |
||||||
|
<Box sx={{ mb: 2 }}> |
||||||
|
{getSourceIcon(source.icon)} |
||||||
|
</Box> |
||||||
|
<Typography variant="h6" component="h2" gutterBottom> |
||||||
|
{source.name} |
||||||
|
</Typography> |
||||||
|
<Typography variant="body2" color="text.secondary"> |
||||||
|
{source.description} |
||||||
|
</Typography> |
||||||
|
</CardContent> |
||||||
|
<CardActions sx={{ justifyContent: 'center', pb: 2 }}> |
||||||
|
<Button |
||||||
|
variant="contained" |
||||||
|
onClick={() => handleImportClick(source)} |
||||||
|
disabled={!source.isAvailable} |
||||||
|
startIcon={<CloudDownload />} |
||||||
|
> |
||||||
|
Import from {source.name} |
||||||
|
</Button> |
||||||
|
</CardActions> |
||||||
|
</Card> |
||||||
|
</Grid> |
||||||
|
))} |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<Dialog |
||||||
|
open={isDialogOpen} |
||||||
|
onClose={handleCloseDialog} |
||||||
|
maxWidth="sm" |
||||||
|
fullWidth |
||||||
|
> |
||||||
|
<DialogTitle> |
||||||
|
{isLoading ? 'Importing Contacts...' : importComplete ? 'Import Complete!' : `Import from ${selectedSource?.name}`} |
||||||
|
</DialogTitle> |
||||||
|
<DialogContent> |
||||||
|
{isLoading && ( |
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', py: 3 }}> |
||||||
|
<CircularProgress size={60} sx={{ mb: 2 }} /> |
||||||
|
<Typography variant="body1"> |
||||||
|
Importing your contacts from {selectedSource?.name}... |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
)} |
||||||
|
|
||||||
|
{importComplete && ( |
||||||
|
<Box sx={{ textAlign: 'center', py: 2 }}> |
||||||
|
<Alert severity="success" sx={{ mb: 2 }}> |
||||||
|
Successfully imported {importedCount} contacts! |
||||||
|
</Alert> |
||||||
|
<Typography variant="body1"> |
||||||
|
Redirecting to your contact list... |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
)} |
||||||
|
|
||||||
|
{!isLoading && !importComplete && ( |
||||||
|
<Typography variant="body1"> |
||||||
|
Are you sure you want to import contacts from {selectedSource?.name}?
|
||||||
|
This will add new contacts to your network. |
||||||
|
</Typography> |
||||||
|
)} |
||||||
|
</DialogContent> |
||||||
|
<DialogActions> |
||||||
|
{!isLoading && !importComplete && ( |
||||||
|
<> |
||||||
|
<Button onClick={handleCloseDialog}>Cancel</Button> |
||||||
|
<Button onClick={handleConfirmImport} variant="contained"> |
||||||
|
Confirm Import |
||||||
|
</Button> |
||||||
|
</> |
||||||
|
)} |
||||||
|
</DialogActions> |
||||||
|
</Dialog> |
||||||
|
</Container> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default ImportPage; |
@ -0,0 +1,309 @@ |
|||||||
|
import { useState, useEffect } from 'react'; |
||||||
|
import { useNavigate } from 'react-router-dom'; |
||||||
|
import { |
||||||
|
Container, |
||||||
|
Typography, |
||||||
|
Box, |
||||||
|
Paper, |
||||||
|
Button, |
||||||
|
Card, |
||||||
|
CardContent, |
||||||
|
Grid, |
||||||
|
Divider, |
||||||
|
IconButton, |
||||||
|
Snackbar, |
||||||
|
Alert, |
||||||
|
TextField, |
||||||
|
InputAdornment |
||||||
|
} from '@mui/material'; |
||||||
|
import { |
||||||
|
Share, |
||||||
|
ContentCopy, |
||||||
|
Email, |
||||||
|
Message, |
||||||
|
WhatsApp, |
||||||
|
GetApp, |
||||||
|
Refresh |
||||||
|
} from '@mui/icons-material'; |
||||||
|
import { QRCodeSVG } from 'qrcode.react'; |
||||||
|
|
||||||
|
const InvitationPage = () => { |
||||||
|
const [invitationUrl, setInvitationUrl] = useState(''); |
||||||
|
const [copySuccess, setCopySuccess] = useState(false); |
||||||
|
const [invitationId, setInvitationId] = useState(''); |
||||||
|
const navigate = useNavigate(); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const generateInvitation = () => { |
||||||
|
const id = Math.random().toString(36).substring(2, 15); |
||||||
|
setInvitationId(id); |
||||||
|
const url = `${window.location.origin}/onboarding?invite=${id}`; |
||||||
|
setInvitationUrl(url); |
||||||
|
}; |
||||||
|
|
||||||
|
generateInvitation(); |
||||||
|
}, []); |
||||||
|
|
||||||
|
const handleCopyToClipboard = async () => { |
||||||
|
try { |
||||||
|
await navigator.clipboard.writeText(invitationUrl); |
||||||
|
setCopySuccess(true); |
||||||
|
} catch (err) { |
||||||
|
console.error('Failed to copy:', err); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleShare = async () => { |
||||||
|
if (navigator.share) { |
||||||
|
try { |
||||||
|
await navigator.share({ |
||||||
|
title: 'Join My Network', |
||||||
|
text: 'Join my personal network to stay connected!', |
||||||
|
url: invitationUrl, |
||||||
|
}); |
||||||
|
} catch (err) { |
||||||
|
console.error('Error sharing:', err); |
||||||
|
} |
||||||
|
} else { |
||||||
|
handleCopyToClipboard(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleEmailShare = () => { |
||||||
|
const subject = encodeURIComponent('Join My Network'); |
||||||
|
const body = encodeURIComponent( |
||||||
|
`I'd like to add you to my personal network. Please use this link to join: ${invitationUrl}` |
||||||
|
); |
||||||
|
window.open(`mailto:?subject=${subject}&body=${body}`); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleWhatsAppShare = () => { |
||||||
|
const text = encodeURIComponent( |
||||||
|
`Join my personal network: ${invitationUrl}` |
||||||
|
); |
||||||
|
window.open(`https://wa.me/?text=${text}`); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleSMSShare = () => { |
||||||
|
const text = encodeURIComponent( |
||||||
|
`Join my personal network: ${invitationUrl}` |
||||||
|
); |
||||||
|
window.open(`sms:?body=${text}`); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleDownloadQR = () => { |
||||||
|
const svg = document.querySelector('#qr-code-svg') as SVGElement; |
||||||
|
if (svg) { |
||||||
|
const svgData = new XMLSerializer().serializeToString(svg); |
||||||
|
const canvas = document.createElement('canvas'); |
||||||
|
const ctx = canvas.getContext('2d'); |
||||||
|
const img = new Image(); |
||||||
|
|
||||||
|
img.onload = () => { |
||||||
|
canvas.width = img.width; |
||||||
|
canvas.height = img.height; |
||||||
|
ctx?.drawImage(img, 0, 0); |
||||||
|
|
||||||
|
const pngFile = canvas.toDataURL('image/png'); |
||||||
|
const downloadLink = document.createElement('a'); |
||||||
|
downloadLink.download = 'network-invitation-qr.png'; |
||||||
|
downloadLink.href = pngFile; |
||||||
|
downloadLink.click(); |
||||||
|
}; |
||||||
|
|
||||||
|
img.src = `data:image/svg+xml;base64,${btoa(svgData)}`; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleNewInvitation = () => { |
||||||
|
const id = Math.random().toString(36).substring(2, 15); |
||||||
|
setInvitationId(id); |
||||||
|
const url = `${window.location.origin}/onboarding?invite=${id}`; |
||||||
|
setInvitationUrl(url); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleGoToContacts = () => { |
||||||
|
navigate('/contacts'); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Container maxWidth="md" sx={{ py: 4 }}> |
||||||
|
<Box sx={{ textAlign: 'center', mb: 4 }}> |
||||||
|
<Typography variant="h3" component="h1" gutterBottom> |
||||||
|
Invite to Your Network |
||||||
|
</Typography> |
||||||
|
<Typography variant="h6" color="text.secondary"> |
||||||
|
Share your personal network invitation |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
|
||||||
|
<Grid container spacing={4}> |
||||||
|
<Grid size={{ xs: 12, md: 6 }}> |
||||||
|
<Paper sx={{ p: 3, textAlign: 'center' }}> |
||||||
|
<Typography variant="h6" gutterBottom> |
||||||
|
QR Code |
||||||
|
</Typography> |
||||||
|
<Box sx={{ mb: 2 }}> |
||||||
|
<QRCodeSVG |
||||||
|
id="qr-code-svg" |
||||||
|
value={invitationUrl} |
||||||
|
size={200} |
||||||
|
level="M" |
||||||
|
includeMargin={true} |
||||||
|
bgColor="#ffffff" |
||||||
|
fgColor="#000000" |
||||||
|
/> |
||||||
|
</Box> |
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}> |
||||||
|
Scan to join your network |
||||||
|
</Typography> |
||||||
|
<Box sx={{ display: 'flex', gap: 1, justifyContent: 'center' }}> |
||||||
|
<Button |
||||||
|
variant="outlined" |
||||||
|
startIcon={<GetApp />} |
||||||
|
onClick={handleDownloadQR} |
||||||
|
size="small" |
||||||
|
> |
||||||
|
Download |
||||||
|
</Button> |
||||||
|
<Button |
||||||
|
variant="outlined" |
||||||
|
startIcon={<Refresh />} |
||||||
|
onClick={handleNewInvitation} |
||||||
|
size="small" |
||||||
|
> |
||||||
|
New QR |
||||||
|
</Button> |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}> |
||||||
|
<Paper sx={{ p: 3 }}> |
||||||
|
<Typography variant="h6" gutterBottom> |
||||||
|
Share Link |
||||||
|
</Typography> |
||||||
|
<TextField |
||||||
|
fullWidth |
||||||
|
value={invitationUrl} |
||||||
|
InputProps={{ |
||||||
|
readOnly: true, |
||||||
|
endAdornment: ( |
||||||
|
<InputAdornment position="end"> |
||||||
|
<IconButton |
||||||
|
onClick={handleCopyToClipboard} |
||||||
|
edge="end" |
||||||
|
size="small" |
||||||
|
> |
||||||
|
<ContentCopy /> |
||||||
|
</IconButton> |
||||||
|
</InputAdornment> |
||||||
|
), |
||||||
|
}} |
||||||
|
sx={{ mb: 2 }} |
||||||
|
/> |
||||||
|
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}> |
||||||
|
Invitation ID: {invitationId} |
||||||
|
</Typography> |
||||||
|
|
||||||
|
<Divider sx={{ my: 2 }} /> |
||||||
|
|
||||||
|
<Typography variant="subtitle1" gutterBottom> |
||||||
|
Share via: |
||||||
|
</Typography> |
||||||
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}> |
||||||
|
<Button |
||||||
|
variant="outlined" |
||||||
|
startIcon={<Share />} |
||||||
|
onClick={handleShare} |
||||||
|
size="small" |
||||||
|
> |
||||||
|
Share |
||||||
|
</Button> |
||||||
|
<Button |
||||||
|
variant="outlined" |
||||||
|
startIcon={<Email />} |
||||||
|
onClick={handleEmailShare} |
||||||
|
size="small" |
||||||
|
> |
||||||
|
Email |
||||||
|
</Button> |
||||||
|
<Button |
||||||
|
variant="outlined" |
||||||
|
startIcon={<WhatsApp />} |
||||||
|
onClick={handleWhatsAppShare} |
||||||
|
size="small" |
||||||
|
sx={{ color: '#25D366' }} |
||||||
|
> |
||||||
|
WhatsApp |
||||||
|
</Button> |
||||||
|
<Button |
||||||
|
variant="outlined" |
||||||
|
startIcon={<Message />} |
||||||
|
onClick={handleSMSShare} |
||||||
|
size="small" |
||||||
|
> |
||||||
|
SMS |
||||||
|
</Button> |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
</Grid> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<Card sx={{ mt: 4 }}> |
||||||
|
<CardContent> |
||||||
|
<Typography variant="h6" gutterBottom> |
||||||
|
How it works |
||||||
|
</Typography> |
||||||
|
<Typography variant="body1" paragraph> |
||||||
|
When someone scans your QR code or clicks your invitation link, they'll be guided through a simple onboarding process: |
||||||
|
</Typography> |
||||||
|
<Box component="ol" sx={{ pl: 2 }}> |
||||||
|
<li> |
||||||
|
<Typography variant="body2"> |
||||||
|
They'll enter their basic information (name, email, etc.) |
||||||
|
</Typography> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<Typography variant="body2"> |
||||||
|
They can connect their social accounts for easier contact sharing |
||||||
|
</Typography> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<Typography variant="body2"> |
||||||
|
They'll be added to your network automatically |
||||||
|
</Typography> |
||||||
|
</li> |
||||||
|
</Box> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
|
||||||
|
<Box sx={{ textAlign: 'center', mt: 4 }}> |
||||||
|
<Button |
||||||
|
variant="contained" |
||||||
|
onClick={handleGoToContacts} |
||||||
|
size="large" |
||||||
|
> |
||||||
|
View My Contacts |
||||||
|
</Button> |
||||||
|
</Box> |
||||||
|
|
||||||
|
<Snackbar |
||||||
|
open={copySuccess} |
||||||
|
autoHideDuration={3000} |
||||||
|
onClose={() => setCopySuccess(false)} |
||||||
|
> |
||||||
|
<Alert |
||||||
|
onClose={() => setCopySuccess(false)} |
||||||
|
severity="success" |
||||||
|
sx={{ width: '100%' }} |
||||||
|
> |
||||||
|
Invitation link copied to clipboard! |
||||||
|
</Alert> |
||||||
|
</Snackbar> |
||||||
|
</Container> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default InvitationPage; |
@ -0,0 +1,127 @@ |
|||||||
|
import { useEffect } from 'react'; |
||||||
|
import { useNavigate, useSearchParams } from 'react-router-dom'; |
||||||
|
import { |
||||||
|
Container, |
||||||
|
Typography, |
||||||
|
Box, |
||||||
|
Paper, |
||||||
|
Stepper, |
||||||
|
Step, |
||||||
|
StepLabel, |
||||||
|
Button, |
||||||
|
LinearProgress |
||||||
|
} from '@mui/material'; |
||||||
|
import { ArrowBack, ArrowForward } from '@mui/icons-material'; |
||||||
|
import { useOnboarding } from '../context/OnboardingContext'; |
||||||
|
import BasicInfoStep from '../components/onboarding/BasicInfoStep'; |
||||||
|
import ConnectAccountsStep from '../components/onboarding/ConnectAccountsStep'; |
||||||
|
|
||||||
|
const OnboardingPage = () => { |
||||||
|
const { state, nextStep, prevStep, completeOnboarding } = useOnboarding(); |
||||||
|
const navigate = useNavigate(); |
||||||
|
const [searchParams] = useSearchParams(); |
||||||
|
const inviteId = searchParams.get('invite'); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (inviteId) { |
||||||
|
console.log('Onboarding with invitation:', inviteId); |
||||||
|
} |
||||||
|
}, [inviteId]); |
||||||
|
|
||||||
|
const steps = [ |
||||||
|
{ |
||||||
|
label: 'Basic Information', |
||||||
|
component: <BasicInfoStep />, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: 'Connect Accounts', |
||||||
|
component: <ConnectAccountsStep />, |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
const handleNext = () => { |
||||||
|
if (state.currentStep === state.totalSteps - 1) { |
||||||
|
completeOnboarding(); |
||||||
|
navigate('/contacts'); |
||||||
|
} else { |
||||||
|
nextStep(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleBack = () => { |
||||||
|
if (state.currentStep === 0) { |
||||||
|
navigate('/'); |
||||||
|
} else { |
||||||
|
prevStep(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const isNextDisabled = () => { |
||||||
|
if (state.currentStep === 0) { |
||||||
|
return !state.userProfile.firstName || !state.userProfile.lastName || !state.userProfile.email; |
||||||
|
} |
||||||
|
return false; |
||||||
|
}; |
||||||
|
|
||||||
|
const progress = ((state.currentStep + 1) / state.totalSteps) * 100; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Container maxWidth="md" sx={{ py: 4 }}> |
||||||
|
<Box sx={{ textAlign: 'center', mb: 4 }}> |
||||||
|
<Typography variant="h3" component="h1" gutterBottom> |
||||||
|
Welcome to Your Network |
||||||
|
</Typography> |
||||||
|
<Typography variant="h6" color="text.secondary"> |
||||||
|
Let's get you set up in just a few steps |
||||||
|
</Typography> |
||||||
|
{inviteId && ( |
||||||
|
<Typography variant="body2" color="primary" sx={{ mt: 1 }}> |
||||||
|
Joining via invitation: {inviteId} |
||||||
|
</Typography> |
||||||
|
)} |
||||||
|
</Box> |
||||||
|
|
||||||
|
<Paper sx={{ p: 3, mb: 3 }}> |
||||||
|
<Box sx={{ mb: 3 }}> |
||||||
|
<LinearProgress variant="determinate" value={progress} sx={{ mb: 2 }} /> |
||||||
|
<Typography variant="body2" color="text.secondary" align="center"> |
||||||
|
Step {state.currentStep + 1} of {state.totalSteps} |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
|
||||||
|
<Stepper activeStep={state.currentStep} alternativeLabel sx={{ mb: 4 }}> |
||||||
|
{steps.map((step) => ( |
||||||
|
<Step key={step.label}> |
||||||
|
<StepLabel>{step.label}</StepLabel> |
||||||
|
</Step> |
||||||
|
))} |
||||||
|
</Stepper> |
||||||
|
|
||||||
|
<Box sx={{ minHeight: '400px' }}> |
||||||
|
{steps[state.currentStep]?.component} |
||||||
|
</Box> |
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 4 }}> |
||||||
|
<Button |
||||||
|
onClick={handleBack} |
||||||
|
startIcon={<ArrowBack />} |
||||||
|
variant="outlined" |
||||||
|
> |
||||||
|
{state.currentStep === 0 ? 'Cancel' : 'Back'} |
||||||
|
</Button> |
||||||
|
|
||||||
|
<Button |
||||||
|
onClick={handleNext} |
||||||
|
endIcon={<ArrowForward />} |
||||||
|
variant="contained" |
||||||
|
disabled={isNextDisabled()} |
||||||
|
> |
||||||
|
{state.currentStep === state.totalSteps - 1 ? 'Complete' : 'Next'} |
||||||
|
</Button> |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
</Container> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default OnboardingPage; |
@ -0,0 +1,84 @@ |
|||||||
|
import type { Contact, ImportSource } from '../types/contact'; |
||||||
|
|
||||||
|
export const dataService = { |
||||||
|
async getContacts(): Promise<Contact[]> { |
||||||
|
return new Promise(async (resolve) => { |
||||||
|
setTimeout(async () => { |
||||||
|
try { |
||||||
|
const response = await fetch('/contacts.json'); |
||||||
|
const contactsData = await response.json(); |
||||||
|
const contacts = contactsData.map((contact: any) => ({ |
||||||
|
...contact, |
||||||
|
createdAt: new Date(contact.createdAt), |
||||||
|
updatedAt: new Date(contact.updatedAt) |
||||||
|
})); |
||||||
|
resolve(contacts); |
||||||
|
} catch (error) { |
||||||
|
console.error('Failed to load contacts:', error); |
||||||
|
resolve([]); |
||||||
|
} |
||||||
|
}, 500); |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
async getContact(id: string): Promise<Contact | undefined> { |
||||||
|
return new Promise(async (resolve) => { |
||||||
|
setTimeout(async () => { |
||||||
|
try { |
||||||
|
const response = await fetch('/contacts.json'); |
||||||
|
const contactsData = await response.json(); |
||||||
|
const contact = contactsData.find((c: any) => c.id === id); |
||||||
|
if (contact) { |
||||||
|
resolve({ |
||||||
|
...contact, |
||||||
|
createdAt: new Date(contact.createdAt), |
||||||
|
updatedAt: new Date(contact.updatedAt) |
||||||
|
}); |
||||||
|
} else { |
||||||
|
resolve(undefined); |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
console.error('Failed to load contact:', error); |
||||||
|
resolve(undefined); |
||||||
|
} |
||||||
|
}, 300); |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
async getImportSources(): Promise<ImportSource[]> { |
||||||
|
return new Promise(async (resolve) => { |
||||||
|
setTimeout(async () => { |
||||||
|
try { |
||||||
|
const response = await fetch('/import-sources.json'); |
||||||
|
const importSourcesData = await response.json(); |
||||||
|
resolve(importSourcesData); |
||||||
|
} catch (error) { |
||||||
|
console.error('Failed to load import sources:', error); |
||||||
|
resolve([]); |
||||||
|
} |
||||||
|
}, 200); |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
async importFromSource(sourceId: string): Promise<Contact[]> { |
||||||
|
return new Promise(async (resolve) => { |
||||||
|
setTimeout(async () => { |
||||||
|
try { |
||||||
|
const response = await fetch('/contacts.json'); |
||||||
|
const contactsData = await response.json(); |
||||||
|
const filteredContacts = contactsData |
||||||
|
.filter((contact: any) => contact.source === sourceId) |
||||||
|
.map((contact: any) => ({ |
||||||
|
...contact, |
||||||
|
createdAt: new Date(contact.createdAt), |
||||||
|
updatedAt: new Date(contact.updatedAt) |
||||||
|
})); |
||||||
|
resolve(filteredContacts); |
||||||
|
} catch (error) { |
||||||
|
console.error('Failed to import from source:', error); |
||||||
|
resolve([]); |
||||||
|
} |
||||||
|
}, 2000); |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
@ -0,0 +1,24 @@ |
|||||||
|
export interface Contact { |
||||||
|
id: string; |
||||||
|
name: string; |
||||||
|
email: string; |
||||||
|
phone?: string; |
||||||
|
company?: string; |
||||||
|
position?: string; |
||||||
|
source: 'linkedin' | 'contacts'; |
||||||
|
profileImage?: string; |
||||||
|
linkedinUrl?: string; |
||||||
|
notes?: string; |
||||||
|
tags?: string[]; |
||||||
|
createdAt: Date; |
||||||
|
updatedAt: Date; |
||||||
|
} |
||||||
|
|
||||||
|
export interface ImportSource { |
||||||
|
id: string; |
||||||
|
name: string; |
||||||
|
type: 'linkedin' | 'contacts'; |
||||||
|
icon: string; |
||||||
|
description: string; |
||||||
|
isAvailable: boolean; |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
export interface UserProfile { |
||||||
|
firstName: string; |
||||||
|
lastName: string; |
||||||
|
email: string; |
||||||
|
phone?: string; |
||||||
|
company?: string; |
||||||
|
position?: string; |
||||||
|
bio?: string; |
||||||
|
} |
||||||
|
|
||||||
|
export interface ConnectedAccount { |
||||||
|
id: string; |
||||||
|
type: 'linkedin' | 'contacts' | 'google' | 'apple'; |
||||||
|
name: string; |
||||||
|
email?: string; |
||||||
|
isConnected: boolean; |
||||||
|
connectedAt?: Date; |
||||||
|
} |
||||||
|
|
||||||
|
export interface OnboardingState { |
||||||
|
currentStep: number; |
||||||
|
totalSteps: number; |
||||||
|
userProfile: Partial<UserProfile>; |
||||||
|
connectedAccounts: ConnectedAccount[]; |
||||||
|
isComplete: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export interface OnboardingContextType { |
||||||
|
state: OnboardingState; |
||||||
|
updateProfile: (profile: Partial<UserProfile>) => void; |
||||||
|
connectAccount: (accountId: string) => void; |
||||||
|
disconnectAccount: (accountId: string) => void; |
||||||
|
nextStep: () => void; |
||||||
|
prevStep: () => void; |
||||||
|
completeOnboarding: () => void; |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
/// <reference types="vite/client" />
|
@ -0,0 +1,28 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |
||||||
|
"target": "ES2022", |
||||||
|
"useDefineForClassFields": true, |
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"], |
||||||
|
"module": "ESNext", |
||||||
|
"skipLibCheck": true, |
||||||
|
|
||||||
|
/* Bundler mode */ |
||||||
|
"moduleResolution": "bundler", |
||||||
|
"allowImportingTsExtensions": true, |
||||||
|
"verbatimModuleSyntax": true, |
||||||
|
"moduleDetection": "force", |
||||||
|
"noEmit": true, |
||||||
|
"jsx": "react-jsx", |
||||||
|
|
||||||
|
/* Linting */ |
||||||
|
"strict": true, |
||||||
|
"noUnusedLocals": true, |
||||||
|
"noUnusedParameters": true, |
||||||
|
"erasableSyntaxOnly": true, |
||||||
|
"noFallthroughCasesInSwitch": true, |
||||||
|
"noUncheckedSideEffectImports": true, |
||||||
|
"resolveJsonModule": true |
||||||
|
}, |
||||||
|
"include": ["src", "data"] |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"files": [], |
||||||
|
"references": [ |
||||||
|
{ "path": "./tsconfig.app.json" }, |
||||||
|
{ "path": "./tsconfig.node.json" } |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", |
||||||
|
"target": "ES2023", |
||||||
|
"lib": ["ES2023"], |
||||||
|
"module": "ESNext", |
||||||
|
"skipLibCheck": true, |
||||||
|
|
||||||
|
/* Bundler mode */ |
||||||
|
"moduleResolution": "bundler", |
||||||
|
"allowImportingTsExtensions": true, |
||||||
|
"verbatimModuleSyntax": true, |
||||||
|
"moduleDetection": "force", |
||||||
|
"noEmit": true, |
||||||
|
|
||||||
|
/* Linting */ |
||||||
|
"strict": true, |
||||||
|
"noUnusedLocals": true, |
||||||
|
"noUnusedParameters": true, |
||||||
|
"erasableSyntaxOnly": true, |
||||||
|
"noFallthroughCasesInSwitch": true, |
||||||
|
"noUncheckedSideEffectImports": true |
||||||
|
}, |
||||||
|
"include": ["vite.config.ts"] |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
import { defineConfig } from 'vite' |
||||||
|
import react from '@vitejs/plugin-react' |
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({ |
||||||
|
plugins: [react()], |
||||||
|
assetsInclude: ['**/*.json'], |
||||||
|
}) |
Loading…
Reference in new issue