diff --git a/.gitignore b/.gitignore index c02a930..6be085b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,10 @@ node_modules dist out .DS_Store -*.log* \ No newline at end of file +*.log* + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/package-lock.json b/package-lock.json index 3fa3389..2df45fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,23 +11,28 @@ "dependencies": { "@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/utils": "^4.0.0", - "antd": "^5.24.3", "chokidar": "^4.0.3", "dbffile": "^1.12.0", "electron-log": "^5.3.2", "electron-store": "^10.0.1", "electron-updater": "^6.3.9", - "firebase": "^11.4.0", - "lodash": "^4.17.21" + "i18next": "^24.2.2", + "lodash": "^4.17.21", + "playwright": "^1.51.0", + "react-error-boundary": "^5.0.0", + "react-i18next": "^15.4.1" }, "devDependencies": { + "@ant-design/v5-patch-for-react-19": "^1.0.3", "@electron-toolkit/eslint-config-prettier": "^3.0.0", "@electron-toolkit/eslint-config-ts": "^3.0.0", "@electron-toolkit/tsconfig": "^1.0.1", + "@playwright/test": "^1.51.0", "@types/node": "^22.13.10", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@vitejs/plugin-react": "^4.3.4", + "antd": "^5.24.3", "electron": "^35.0.1", "electron-builder": "^25.1.8", "electron-vite": "^3.0.0", @@ -35,9 +40,11 @@ "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", + "firebase": "^11.4.0", "prettier": "^3.5.3", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-router": "^7.3.0", "typescript": "^5.8.2", "vite": "^6.2.1" } @@ -60,6 +67,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.0.tgz", "integrity": "sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A==", + "dev": true, "license": "MIT", "dependencies": { "@ant-design/fast-color": "^2.0.6" @@ -69,6 +77,7 @@ "version": "1.23.0", "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.23.0.tgz", "integrity": "sha512-7GAg9bD/iC9ikWatU9ym+P9ugJhi/WbsTWzcKN6T4gU0aehsprtke1UAaaSxxkjjmkJb3llet/rbUSLPgwlY4w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", @@ -88,6 +97,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "dev": true, "license": "MIT", "dependencies": { "@ant-design/cssinjs": "^1.21.0", @@ -103,6 +113,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz", "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.24.7" @@ -115,6 +126,7 @@ "version": "5.6.1", "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "dev": true, "license": "MIT", "dependencies": { "@ant-design/colors": "^7.0.0", @@ -135,12 +147,14 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "dev": true, "license": "MIT" }, "node_modules/@ant-design/react-slick": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.4", @@ -153,6 +167,21 @@ "react": ">=16.9.0" } }, + "node_modules/@ant-design/v5-patch-for-react-19": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@ant-design/v5-patch-for-react-19/-/v5-patch-for-react-19-1.0.3.tgz", + "integrity": "sha512-iWfZuSUl5kuhqLUw7jJXUQFMMkM7XpW7apmKzQBQHU0cpifYW4A79xIBt9YVO5IBajKpPG5UKP87Ft7Yrw1p/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "antd": ">=5.22.6", + "react": ">=19.0.0", + "react-dom": ">=19.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -892,12 +921,14 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "dev": true, "license": "MIT" }, "node_modules/@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "dev": true, "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { @@ -1528,6 +1559,7 @@ "version": "0.10.12", "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.12.tgz", "integrity": "sha512-iDCGnw6qdFqwI5ywkgece99WADJNoymu+nLIQI4fZM/vCZ3bEo4wlpEetW71s1HqGpI0hQStiPhqVjFxDb2yyw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -1544,6 +1576,7 @@ "version": "0.2.18", "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.18.tgz", "integrity": "sha512-Hw9mzsSMZaQu6wrTbi3kYYwGw9nBqOHr47pVLxfr5v8CalsdrG5gfs9XUlPOZjHRVISp3oQrh1j7d3E+ulHPjQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/analytics": "0.10.12", @@ -1560,12 +1593,14 @@ "version": "0.8.3", "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz", "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/app": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.11.2.tgz", "integrity": "sha512-bFee0hPJZBzNtiizRxdgsu8C9DW3mn1y0OJJ4zHQsccjDYzGOfvN0G3CMGyBIiwNctsFpQa8orbp2IKywoUeqA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -1582,6 +1617,7 @@ "version": "0.8.12", "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.12.tgz", "integrity": "sha512-LxjcoIFOU4sgK07ZWb8XDHxuVB+UKs41vPK+Sg9PeZMvEoz84fndFAx8Nz2nipiya2EmyxBgVhff8Hi6GBt+XA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -1600,6 +1636,7 @@ "version": "0.3.19", "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.19.tgz", "integrity": "sha512-G8FMiqhrKc4gEEujrBDBBrbRav8MGqoLObWj1hy/riCSg4XlRYhpnq3ev8E9HTirqU1tAGH6oJl7vr+jfM7YNA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/app-check": "0.8.12", @@ -1620,18 +1657,21 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/app-check-types": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz", "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/app-compat": { "version": "0.2.51", "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.51.tgz", "integrity": "sha512-pxF1+coABt+ugqNI0YXDlmkKv4kh3pjI5BqIJJ1VXBo42OZbKMsQbFeos14YBrWwiqqSjUvQ70FBNsv5E2wuxg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/app": "0.11.2", @@ -1648,12 +1688,14 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/auth": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.9.1.tgz", "integrity": "sha512-9KKo5SNVkyJzftsW+daS+PGDbeJ+MFJWXQFHDqqPPH3acWHtiNnGHH5HGpIJErEELrsm9xMPie5zfZ0XpGU8+w==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -1678,6 +1720,7 @@ "version": "0.5.19", "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.19.tgz", "integrity": "sha512-v898POphOIBJliKF76SiGOXh4EdhO5fM6S9a2ZKf/8wHdBea/qwxwZoVVya4DW6Mi7vWyp1lIzHbFgwRz8G9TA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/auth": "1.9.1", @@ -1697,12 +1740,14 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/auth-types": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz", "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==", + "dev": true, "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x", @@ -1713,6 +1758,7 @@ "version": "0.6.13", "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.13.tgz", "integrity": "sha512-I/Eg1NpAtZ8AAfq8mpdfXnuUpcLxIDdCDtTzWSh+FXnp/9eCKJ3SNbOCKrUCyhLzNa2SiPJYruei0sxVjaOTeg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/util": "1.11.0", @@ -1726,6 +1772,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.1.tgz", "integrity": "sha512-PNlfAJ2mcbyRlWfm41nfk8EksTuvMFTFIX+puNzeUa6OTIDtyp1IX1NJVc7n6WpfbErN7tNqcOEMe6BMtpcjVA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/auth-interop-types": "0.2.4", @@ -1742,6 +1789,7 @@ "version": "1.0.13", "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.13.tgz", "integrity": "sha512-cdc+LuseKdJXzlrCx8ePMXyctSWtYS9SsP3y7EeA85GzNh/IL0b7HOq0eShridL935iQ0KScZCj5qJtKkGE53g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", @@ -1760,6 +1808,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.4.tgz", "integrity": "sha512-4qsptwZ3DTGNBje56ETItZQyA/HMalOelnLmkC3eR0M6+zkzOHjNHyWUWodW2mqxRKAM0sGkn+aIwYHKZFJXug==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -1777,6 +1826,7 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.9.tgz", "integrity": "sha512-uCntrxPbJHhZsNRpMhxNCm7GzhYWX+7J2e57wq1ZZ4NJrQw5DORgkAzJMByYZcVAjgADnCxxhK/GkoypH+XpvQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/app-types": "0.9.3", @@ -1787,6 +1837,7 @@ "version": "4.7.9", "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.9.tgz", "integrity": "sha512-uq/bUtHDqJ5ZqPHAJIlNzHpXUtcVYcASz2V6y7UmP1WLlRKEt1yf1OcQW5u8pY2yq7162OnCl5J5mkOdMTMLZw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -1808,6 +1859,7 @@ "version": "0.3.44", "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.44.tgz", "integrity": "sha512-4Lv2TyHEW+FugXPgmQ0ZylSbh9uFuKDP0lCL1hX9cbxXaafhC/Nww+DWokUQ2zZcynjc8fxFunw6Xbd3QHAlgA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -1827,6 +1879,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz", "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==", + "dev": true, "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x", @@ -1837,6 +1890,7 @@ "version": "0.12.3", "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.12.3.tgz", "integrity": "sha512-Wv7JZMUkKLb1goOWRtsu3t7m97uK6XQvjQLPvn8rncY91+VgdU72crqnaYCDI/ophNuBEmuK8mn0/pAnjUeA6A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", @@ -1857,6 +1911,7 @@ "version": "0.3.20", "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.20.tgz", "integrity": "sha512-iIudmYDAML6n3c7uXO2YTlzra2/J6lnMzmJTXNthvrKVMgNMaseNoQP1wKfchK84hMuSF8EkM4AvufwbJ+Juew==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -1876,12 +1931,14 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz", "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/installations": { "version": "0.6.13", "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.13.tgz", "integrity": "sha512-6ZpkUiaygPFwgVneYxuuOuHnSPnTA4KefLEaw/sKk/rNYgC7X6twaGfYb0sYLpbi9xV4i5jXsqZ3WO+yaguNgg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -1897,6 +1954,7 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.13.tgz", "integrity": "sha512-f/o6MqCI7LD/ulY9gvgkv6w5k6diaReD8BFHd/y/fEdpsXmFWYS/g28GXCB72bRVBOgPpkOUNl+VsMvDwlRKmw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -1913,6 +1971,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz", "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==", + "dev": true, "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x" @@ -1922,6 +1981,7 @@ "version": "0.4.4", "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" @@ -1934,6 +1994,7 @@ "version": "0.12.17", "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.17.tgz", "integrity": "sha512-W3CnGhTm6Nx8XGb6E5/+jZTuxX/EK8Vur4QXvO1DwZta/t0xqWMRgO9vNsZFMYBqFV4o3j4F9qK/iddGYwWS6g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -1951,6 +2012,7 @@ "version": "0.2.17", "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.17.tgz", "integrity": "sha512-5Q+9IG7FuedusdWHVQRjpA3OVD9KUWp/IPegcv0s5qSqRLBjib7FlAeWxN+VL0Ew43tuPJBY2HKhEecuizmO1Q==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -1966,12 +2028,14 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz", "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/performance": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.1.tgz", "integrity": "sha512-SkEUurawojCjav2V2AXo6BQLDtv02NxgXPLCiAvrkn95IAKI4W/UbLKYQvMbEez/nqvmnucLyklcMlB0Q5a1iw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -1989,6 +2053,7 @@ "version": "0.2.14", "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.14.tgz", "integrity": "sha512-/crPg0fDqHIx+FjFoEqWxNp+lJSF40ZG7x43AAJGRaUaWLJDncQm3UJB5/mABaRZb7obs1CQAcRtd4phZFkmZg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -2006,12 +2071,14 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz", "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/remote-config": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.0.tgz", "integrity": "sha512-Yrk4l5+6FJLPHC6irNHMzgTtJ3NfHXlAXVChCBdNFtgmzyGmufNs/sr8oA0auEfIJ5VpXCaThRh3P4OdQxiAlQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -2028,6 +2095,7 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.13.tgz", "integrity": "sha512-UmHoO7TxAEJPIZf8e1Hy6CeFGMeyjqSCpgoBkQZYXFI2JHhzxIyDpr8jVKJJN1dmAePKZ5EX7dC13CmcdTOl7Q==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -2045,12 +2113,14 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz", "integrity": "sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/storage": { "version": "0.13.7", "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.7.tgz", "integrity": "sha512-FkRyc24rK+Y6EaQ1tYFm3TevBnnfSNA0VyTfew2hrYyL/aYfatBg7HOgktUdB4kWMHNA9VoTotzZTGoLuK92wg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -2068,6 +2138,7 @@ "version": "0.3.17", "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.17.tgz", "integrity": "sha512-CBlODWEZ5b6MJWVh21VZioxwxNwVfPA9CAdsk+ZgVocJQQbE2oDW1XJoRcgthRY1HOitgbn4cVrM+NlQtuUYhw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.13", @@ -2087,6 +2158,7 @@ "version": "0.8.3", "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz", "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==", + "dev": true, "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x", @@ -2097,6 +2169,7 @@ "version": "1.11.0", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.11.0.tgz", "integrity": "sha512-PzSrhIr++KI6y4P6C/IdgBNMkEx0Ex6554/cYd0Hm+ovyFSJtJXqb/3OSIdnBoa2cpwZT1/GW56EmRc5qEc5fQ==", + "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -2110,6 +2183,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@firebase/vertexai/-/vertexai-1.1.0.tgz", "integrity": "sha512-K8CgIFKJrfrf5lYhKnDXOu08FEmIzVExK+ApUZx4Bw2GAmLEA3wDVrsjuupuvpXZSp8QlzvEiXwqshqqc4v0pA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", @@ -2130,6 +2204,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz", "integrity": "sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@gar/promisify": { @@ -2143,6 +2218,7 @@ "version": "1.9.15", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.7.8", @@ -2156,6 +2232,7 @@ "version": "0.7.13", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "lodash.camelcase": "^4.3.0", @@ -2574,34 +2651,55 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@playwright/test": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.0.tgz", + "integrity": "sha512-dJ0dMbZeHhI+wb77+ljx/FeC8VBP6j/rj9OAojO08JI80wTZy6vRk9KvHKiDCUh4iMpEiseMgqRBIeW+eKX6RA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.51.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.1", @@ -2612,36 +2710,42 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@rc-component/async-validator": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.24.4" @@ -2654,6 +2758,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz", "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "dev": true, "license": "MIT", "dependencies": { "@ant-design/fast-color": "^2.0.6", @@ -2670,6 +2775,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz", "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -2684,6 +2790,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.0" @@ -2696,6 +2803,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.0", @@ -2714,6 +2822,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz", "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.0", @@ -2732,6 +2841,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.0.0.tgz", "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.24.7", @@ -2750,6 +2860,7 @@ "version": "1.15.1", "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz", "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.0", @@ -2770,6 +2881,7 @@ "version": "2.2.6", "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.6.tgz", "integrity": "sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.2", @@ -3144,6 +3256,13 @@ "@types/responselike": "^1.0.0" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -3651,6 +3770,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3660,6 +3780,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3675,6 +3796,7 @@ "version": "5.24.3", "resolved": "https://registry.npmjs.org/antd/-/antd-5.24.3.tgz", "integrity": "sha512-H5fopyOVRAnegfwLuEdjhPR+l5z3/lo4aQyDsgIYhfmeBcRgN/XNkefVxzRHNuWHeYr9E9LbyxEQcMF91sy5lg==", + "dev": true, "license": "MIT", "dependencies": { "@ant-design/colors": "^7.2.0", @@ -4683,6 +4805,7 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "dev": true, "license": "MIT" }, "node_modules/clean-stack": { @@ -4743,6 +4866,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -4779,6 +4903,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4791,6 +4916,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/color-support": { @@ -4857,6 +4983,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "dev": true, "license": "MIT" }, "node_modules/concat-map": { @@ -5007,10 +5134,21 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "dev": true, "license": "MIT", "dependencies": { "toggle-selection": "^1.0.6" @@ -5082,6 +5220,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -5142,6 +5281,7 @@ "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "dev": true, "license": "MIT" }, "node_modules/dbffile": { @@ -5880,6 +6020,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/encoding": { @@ -6144,6 +6285,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6590,6 +6732,7 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "websocket-driver": ">=0.5.1" @@ -6677,6 +6820,7 @@ "version": "11.4.0", "resolved": "https://registry.npmjs.org/firebase/-/firebase-11.4.0.tgz", "integrity": "sha512-Z6kwhWIPDgIm0+NUEQxwjH14hMP7t42WSFnf/78R0Vh59VovLYTOCTM3MIdY3jlSZ9uKz56FhXrvsNXNhAn/Xg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/analytics": "0.10.12", @@ -6925,6 +7069,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -7307,6 +7452,15 @@ "dev": true, "license": "ISC" }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -7317,6 +7471,7 @@ "version": "0.5.9", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz", "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==", + "dev": true, "license": "MIT" }, "node_modules/http-proxy-agent": { @@ -7370,6 +7525,37 @@ "ms": "^2.0.0" } }, + "node_modules/i18next": { + "version": "24.2.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.2.tgz", + "integrity": "sha512-NE6i86lBCKRYZa5TaUDkU5S4HFgLIEJRLr3Whf2psgaxBleQ2LC1YW1Vc+SCgkAW7VEzndT6al6+CzegSUHcTQ==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -7405,6 +7591,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true, "license": "ISC" }, "node_modules/ieee754": { @@ -7708,6 +7895,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8140,6 +8328,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "dev": true, "license": "MIT", "dependencies": { "string-convert": "^0.2.0" @@ -8296,6 +8485,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, "license": "MIT" }, "node_modules/lodash.defaults": { @@ -8379,6 +8569,7 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz", "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==", + "dev": true, "license": "Apache-2.0" }, "node_modules/loose-envify": { @@ -9349,6 +9540,50 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.0.tgz", + "integrity": "sha512-442pTfGM0xxfCYxuBa/Pu6B2OqxqqaYq39JS8QDMGThUvIOCd6s0ANDog3uwA0cHavVlnTQzGCN7Id2YekDSXA==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.51.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.0.tgz", + "integrity": "sha512-x47yPE3Zwhlil7wlNU/iktF7t2r/URR3VLbH6EknJd/04Qc/PSJ0EY3CMXipmglLG+zyRxW6HNo2EGbKLHPWMg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -9496,6 +9731,7 @@ "version": "7.4.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "dev": true, "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -9573,6 +9809,7 @@ "version": "3.33.1", "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.33.1.tgz", "integrity": "sha512-Kyl4EJ7ZfCBuidmZVieegcbFw0RcU5bHHSbtEdmuLYd0fYHCAiYKZ6zon7fWAVyC6rWWOOib0XKdTSf7ElC9rg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", @@ -9590,6 +9827,7 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.5.0.tgz", "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -9605,6 +9843,7 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz", "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -9621,6 +9860,7 @@ "version": "9.6.0", "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz", "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -9638,6 +9878,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.2.0.tgz", "integrity": "sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", @@ -9655,6 +9896,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz", "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", @@ -9671,6 +9913,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.0.tgz", "integrity": "sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.0", @@ -9689,6 +9932,7 @@ "version": "7.11.0", "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.11.0.tgz", "integrity": "sha512-aZkTEZXqeqfPZtnSdNUnKQA0N/3MbgR7nUnZ+/4MfSFWPFHZau4p5r5ShaI0KPEMnNjv4kijSCFq/9wtJpwykw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.2", @@ -9707,6 +9951,7 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.7.3.tgz", "integrity": "sha512-A5w4egJq8+4JzlQ55FfQjDnPvOaAbzwC3VLOAdOytyek3TboSOP9qxN+Gifup+shVXfvecBLBbWBpWxmk02SWQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", @@ -9722,6 +9967,7 @@ "version": "9.4.0", "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.4.0.tgz", "integrity": "sha512-Tiy4DcXcFXAf9wDhN8aUAyMeCLHJUHA/VA/t7Hj8ZEx5ETvxG7MArDOSE6psbiSCo+vJPm4E3fGN710ITVn6GA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -9739,6 +9985,7 @@ "version": "2.19.1", "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.19.1.tgz", "integrity": "sha512-KK3bAc/bPFI993J3necmaMXD2reZTzytZdlTvkeBbp50IGH1BDPDvxLdHDUrpQx2b2TGaVJsn+86BvYa03kGqA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.22.5", @@ -9758,6 +10005,7 @@ "version": "9.16.1", "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz", "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -9776,6 +10024,7 @@ "version": "2.9.5", "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz", "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", @@ -9791,6 +10040,7 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.3.tgz", "integrity": "sha512-42szwnn8VYQoT6GnjO00i1iwqV9D1TTMvxObWsuLwgl0TsOokzhkYiufdtQBsJMFjJravS1hfDKVMHLKLcPE4g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -9810,6 +10060,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.4.1.tgz", "integrity": "sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", @@ -9826,6 +10077,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.1.0.tgz", "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -9841,6 +10093,7 @@ "version": "4.11.3", "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.11.3.tgz", "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.24.7", @@ -9880,6 +10133,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz", "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -9895,6 +10149,7 @@ "version": "2.13.1", "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.1.tgz", "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -9913,6 +10168,7 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.7", @@ -9929,6 +10185,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.0.tgz", "integrity": "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", @@ -9945,6 +10202,7 @@ "version": "14.16.6", "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.6.tgz", "integrity": "sha512-YPMtRPqfZWOm2XGTbx5/YVr1HT0vn//8QS77At0Gjb3Lv+Lbut0IORJPKLWu1hQ3u4GsA0SrDzs7nI8JG7Zmyg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -9967,6 +10225,7 @@ "version": "11.1.8", "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.8.tgz", "integrity": "sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -9985,6 +10244,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz", "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.16.7", @@ -10003,6 +10263,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz", "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.21.0", @@ -10018,6 +10279,7 @@ "version": "7.50.4", "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.50.4.tgz", "integrity": "sha512-Y+YuncnQqoS5e7yHvfvlv8BmCvwDYDX/2VixTBEhkMDk9itS9aBINp4nhzXFKiBP/frG4w0pS9d9Rgisl0T1Bw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -10039,6 +10301,7 @@ "version": "15.5.1", "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.5.1.tgz", "integrity": "sha512-yiWivLAjEo5d1v2xlseB2dQocsOhkoVSfo1krS8v8r+02K+TBUjSjXIf7dgyVSxp6wRIPv5pMi5hanNUlQMgUA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.2", @@ -10061,6 +10324,7 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.9.0.tgz", "integrity": "sha512-dQW/Bc/MriPBTugj2Kx9PMS5eXCCGn2cxoIaichjbNvOiARlaHdI99j4DTxLl/V8+PIfW06uFy7kjfUIDDKyxQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -10078,6 +10342,7 @@ "version": "6.4.0", "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.4.0.tgz", "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.2", @@ -10094,6 +10359,7 @@ "version": "5.13.1", "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz", "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -10114,6 +10380,7 @@ "version": "5.27.0", "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.27.0.tgz", "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", @@ -10131,6 +10398,7 @@ "version": "4.8.1", "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.8.1.tgz", "integrity": "sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", @@ -10146,6 +10414,7 @@ "version": "5.44.4", "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz", "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", @@ -10160,12 +10429,14 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, "license": "MIT" }, "node_modules/rc-virtual-list": { "version": "3.18.4", "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.18.4.tgz", "integrity": "sha512-qkurwgc4Je4xJaYe1DprDl2fwtfEZcuC4UhsJRiX2YZ6wSZAUPQXH/lIX+ZRtNEWmz3pzSBQ7NX3Csjp0wCtcg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.0", @@ -10194,6 +10465,7 @@ "version": "19.0.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "dev": true, "license": "MIT", "dependencies": { "scheduler": "^0.25.0" @@ -10202,6 +10474,40 @@ "react": "^19.0.0" } }, + "node_modules/react-error-boundary": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-5.0.0.tgz", + "integrity": "sha512-tnjAxG+IkpLephNcePNA7v6F/QpWLH8He65+DmedchDwg162JZqx4NmbXj0mlAYVVEd81OW7aFhmbsScYfiAFQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, + "node_modules/react-i18next": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.1.tgz", + "integrity": "sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -10219,6 +10525,31 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.3.0.tgz", + "integrity": "sha512-466f2W7HIWaNXTKM5nHTqNxLrHTyXybm7R0eBlVSt0k/u55tTCDO194OIx/NrYD4TS5SXKTNekXfT37kMKUjgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/read-binary-file-arch": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", @@ -10339,6 +10670,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10375,6 +10707,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "dev": true, "license": "MIT" }, "node_modules/resolve": { @@ -10580,6 +10913,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -10657,12 +10991,14 @@ "version": "0.25.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "dev": true, "license": "MIT" }, "node_modules/scroll-into-view-if-needed": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "dev": true, "license": "MIT", "dependencies": { "compute-scroll-into-view": "^3.0.2" @@ -10707,6 +11043,13 @@ "dev": true, "license": "ISC" }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "dev": true, + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -11033,12 +11376,14 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "dev": true, "license": "MIT" }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -11167,6 +11512,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11211,6 +11557,7 @@ "version": "4.3.6", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "dev": true, "license": "MIT" }, "node_modules/sumchecker": { @@ -11374,6 +11721,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.22" @@ -11422,6 +11770,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "dev": true, "license": "MIT" }, "node_modules/truncate-utf8-bytes": { @@ -11451,8 +11800,16 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, "license": "0BSD" }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "dev": true, + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -11561,7 +11918,7 @@ "version": "5.8.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -12275,6 +12632,15 @@ "@esbuild/win32-x64": "0.25.1" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -12289,12 +12655,14 @@ "version": "4.2.4", "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", + "dev": true, "license": "Apache-2.0" }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", @@ -12309,6 +12677,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=0.8.0" @@ -12449,6 +12818,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -12501,6 +12871,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -12517,6 +12888,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -12535,6 +12907,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" diff --git a/package.json b/package.json index d21bb56..573c7fd 100644 --- a/package.json +++ b/package.json @@ -23,23 +23,28 @@ "dependencies": { "@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/utils": "^4.0.0", - "antd": "^5.24.3", "chokidar": "^4.0.3", "dbffile": "^1.12.0", "electron-log": "^5.3.2", "electron-store": "^10.0.1", "electron-updater": "^6.3.9", - "firebase": "^11.4.0", - "lodash": "^4.17.21" + "i18next": "^24.2.2", + "lodash": "^4.17.21", + "playwright": "^1.51.0", + "react-error-boundary": "^5.0.0", + "react-i18next": "^15.4.1" }, "devDependencies": { + "@ant-design/v5-patch-for-react-19": "^1.0.3", "@electron-toolkit/eslint-config-prettier": "^3.0.0", "@electron-toolkit/eslint-config-ts": "^3.0.0", "@electron-toolkit/tsconfig": "^1.0.1", + "@playwright/test": "^1.51.0", "@types/node": "^22.13.10", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@vitejs/plugin-react": "^4.3.4", + "antd": "^5.24.3", "electron": "^35.0.1", "electron-builder": "^25.1.8", "electron-vite": "^3.0.0", @@ -47,9 +52,11 @@ "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", + "firebase": "^11.4.0", "prettier": "^3.5.3", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-router": "^7.3.0", "typescript": "^5.8.2", "vite": "^6.2.1" } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..b0d9b0d --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,80 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + //testDir: './tests/**/*', + //testMatch: '**/*.test.ts', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: "npm run start", + // url: "http://127.0.0.1:3000", + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/src/main/index.test.ts b/src/main/index.test.ts new file mode 100644 index 0000000..083cb32 --- /dev/null +++ b/src/main/index.test.ts @@ -0,0 +1,21 @@ +import { _electron as electron } from "playwright"; +import { test, expect } from "@playwright/test"; + +test("example test", async () => { + const electronApp = await electron.launch({ args: ["."] }); + const isPackaged = await electronApp.evaluate(async ({ app }) => { + // This runs in Electron's main process, parameter here is always + // the result of the require('electron') in the main app script. + return app.isPackaged; + }); + + expect(isPackaged).toBe(false); + + // Wait for the first BrowserWindow to open + // and return its Page object + const window = await electronApp.firstWindow(); + await window.screenshot({ path: "intro.png" }); + + // close app + await electronApp.close(); +}); diff --git a/src/main/index.ts b/src/main/index.ts index 892ef99..a2a5616 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -4,7 +4,8 @@ import log from "electron-log/main"; import { join } from "path"; import icon from "../../resources/icon.png?asset"; import ErrorTypeCheck from "../util/errorTypeCheck"; - +import "./store/store"; +log.initialize(); function createWindow(): void { // Create the browser window. const mainWindow = new BrowserWindow({ diff --git a/src/main/ipc/ipcMainConfig.ts b/src/main/ipc/ipcMainConfig.ts index e86ab69..f96cf0d 100644 --- a/src/main/ipc/ipcMainConfig.ts +++ b/src/main/ipc/ipcMainConfig.ts @@ -1,6 +1,38 @@ import { ipcMain } from "electron"; import ipcTypes from "../../util/ipcTypes.json"; +import log from "electron-log/main"; +import { ipcMainHandleAuthStateChanged } from "./ipcMainHandler.user"; +// Log all IPC messages and their payloads + +const logIpcMessages = () => { + // Get all message types from ipcTypes.toMain + Object.keys(ipcTypes.toMain).forEach((key) => { + const messageType = ipcTypes.toMain[key]; + + // Wrap the original handler with our logging + const originalHandler = ipcMain.listeners(messageType)[0]; + if (originalHandler) { + ipcMain.removeAllListeners(messageType); + } + ipcMain.on(messageType, (event, payload) => { + log.info( + `%c[IPC Main]%c${messageType}`, + "color: red", + "color: green", + payload + ); + // Call original handler if it existed + if (originalHandler) { + originalHandler(event, payload); + } + }); + }); +}; ipcMain.on(ipcTypes.toMain.test, (payload: any) => console.log("** Verify that ipcMain is loaded and working.", payload) ); + +ipcMain.on(ipcTypes.toMain.authStateChanged, ipcMainHandleAuthStateChanged); + +logIpcMessages(); diff --git a/src/main/ipc/ipcMainHandler.user.ts b/src/main/ipc/ipcMainHandler.user.ts new file mode 100644 index 0000000..d06fcf3 --- /dev/null +++ b/src/main/ipc/ipcMainHandler.user.ts @@ -0,0 +1,14 @@ +import { IpcMainEvent } from "electron"; +import Store from "../store/store"; +import { User } from "firebase/auth"; +import log from "electron-log/main"; + +const ipcMainHandleAuthStateChanged = async ( + event: IpcMainEvent, + user: User | null +) => { + Store.set("user", user); + log.log(Store.get("user")); +}; + +export { ipcMainHandleAuthStateChanged }; diff --git a/src/main/store/store.ts b/src/main/store/store.ts index 5764c05..2c95469 100644 --- a/src/main/store/store.ts +++ b/src/main/store/store.ts @@ -8,6 +8,7 @@ const store = new Store({ enabled: false, pollingInterval: 30000, }, + user: null, }, }); diff --git a/src/renderer/src/App.test.tsx b/src/renderer/src/App.test.tsx new file mode 100644 index 0000000..7a9277f --- /dev/null +++ b/src/renderer/src/App.test.tsx @@ -0,0 +1,93 @@ +import { test, expect } from '@playwright/test'; +import { Page } from '@playwright/test'; + +// src/renderer/src/App.test.tsx + +// Mock data +const mockUser = { + uid: 'test123', + email: 'test@example.com', + displayName: 'Test User', + toJSON: () => ({ + uid: 'test123', + email: 'test@example.com', + displayName: 'Test User' + }) +}; + +test.describe('App Component', () => { + let page: Page; + + test.beforeEach(async ({ browser }) => { + page = await browser.newPage(); + + // Mock Firebase Auth + await page.addInitScript(() => { + window.mockAuthState = null; + + // Mock the firebase auth module + jest.mock('./util/firebase', () => ({ + auth: { + onAuthStateChanged: (callback) => { + callback(window.mockAuthState); + // Return mock unsubscribe function + return () => {}; + } + } + })); + + // Mock electron IPC + window.electron = { + ipcRenderer: { + send: jest.fn() + } + }; + }); + + await page.goto('/'); + }); + + test('should show SignInForm when user is not authenticated', async () => { + await page.evaluate(() => { + window.mockAuthState = null; + }); + + await page.reload(); + + // Check if SignInForm is visible + const signInForm = await page.locator('form').filter({ hasText: 'Sign In' }); + await expect(signInForm).toBeVisible(); + }); + + test('should show routes when user is authenticated', async () => { + await page.evaluate((user) => { + window.mockAuthState = user; + }, mockUser); + + await page.reload(); + + // Check if AuthHome is visible + const authHome = await page.locator('div:text("AuthHome")'); + await expect(authHome).toBeVisible(); + + // Check that electron IPC was called with auth state + await expect(page.evaluate(() => { + return window.electron.ipcRenderer.send.mock.calls.length > 0; + })).resolves.toBe(true); + }); + + test('should navigate to settings page when authenticated', async () => { + await page.evaluate((user) => { + window.mockAuthState = user; + }, mockUser); + + await page.reload(); + + // Navigate to settings + await page.click('a[href="/settings"]'); + + // Check if Settings page is visible + const settingsPage = await page.locator('div:text("Settings")'); + await expect(settingsPage).toBeVisible(); + }); +}); \ No newline at end of file diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 63c5fd8..5e250f9 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1,25 +1,49 @@ -import { Button } from "antd"; -import log from "electron-log/renderer"; +import "@ant-design/v5-patch-for-react-19"; +import { Layout } from "antd"; +import { User } from "firebase/auth"; +import { useState } from "react"; +import { BrowserRouter, Route, Routes } from "react-router"; import ipcTypes from "../../util/ipcTypes.json"; +import NavigationHeader from "./components/NavigationHeader/Navigationheader"; import SignInForm from "./components/SignInForm/SignInForm"; -import Versions from "./components/Versions"; +import { auth } from "./util/firebase"; +import {} from "react-error-boundary"; +import { ErrorBoundary } from "react-error-boundary"; +import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback/ErrorBoundaryFallback"; + +const App: React.FC = () => { + const [user, setUser] = useState(null); + + auth.onAuthStateChanged((user) => { + setUser(user); + //Send back to the main process so that it knows we are authenticated. + if (user) { + window.electron.ipcRenderer.send( + ipcTypes.toMain.authStateChanged, + user.toJSON() + ); + } + }); -function App(): JSX.Element { - const ipcHandle = (): void => window.electron.ipcRenderer.send("ping"); - const ipcHandleWithType = (): void => { - log.error("Test from renderer."); - window.electron.ipcRenderer.send(ipcTypes.toMain.test, { test: "test" }); - }; return ( - <> - - - - {import.meta.env.VITE_FIREBASE_CONFIG} - - - + + + + {!user ? ( + + ) : ( + <> + + + AuthHome} /> + Settings} /> + + + )} + + + ); -} +}; export default App; diff --git a/src/renderer/src/components/ErrorBoundaryFallback/ErrorBoundaryFallback.tsx b/src/renderer/src/components/ErrorBoundaryFallback/ErrorBoundaryFallback.tsx new file mode 100644 index 0000000..e347cb0 --- /dev/null +++ b/src/renderer/src/components/ErrorBoundaryFallback/ErrorBoundaryFallback.tsx @@ -0,0 +1,20 @@ +import { Button, Result } from "antd"; +import { FallbackProps } from "react-error-boundary"; +import { useTranslation } from "react-i18next"; + +const ErrorBoundaryFallback: React.FC = ({ + error, + resetErrorBoundary, +}) => { + const { t } = useTranslation(); + return ( + Try again]} + /> + ); +}; + +export default ErrorBoundaryFallback; diff --git a/src/renderer/src/components/NavigationHeader/Navigationheader.tsx b/src/renderer/src/components/NavigationHeader/Navigationheader.tsx new file mode 100644 index 0000000..942f554 --- /dev/null +++ b/src/renderer/src/components/NavigationHeader/Navigationheader.tsx @@ -0,0 +1,29 @@ +import { Layout, Menu } from "antd"; +import { MenuItemType } from "antd/es/menu/interface"; +import { useTranslation } from "react-i18next"; +import { NavLink } from "react-router"; + +const NavigationHeader: React.FC = () => { + const { t } = useTranslation(); + const menuItems: MenuItemType[] = [ + { label: {t("navigation.home")}, key: "home" }, + { + label: {t("navigation.settings")}, + key: "settings", + }, + ]; + return ( + +
+ + + ); +}; + +export default NavigationHeader; diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index 3fd3a69..7c4d133 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -1,6 +1,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; +import "./util/i18n"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( diff --git a/src/renderer/src/util/i18n.ts b/src/renderer/src/util/i18n.ts new file mode 100644 index 0000000..414aa6c --- /dev/null +++ b/src/renderer/src/util/i18n.ts @@ -0,0 +1,18 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import enTranslations from "../../../util/translations/en-US/renderer.json"; + +const resources = { + en: enTranslations, +}; + +i18n.use(initReactI18next).init({ + resources, + debug: import.meta.env.DEV, + lng: "en", + interpolation: { + escapeValue: false, + }, +}); + +export default i18n; diff --git a/src/util/ipcTypes.json b/src/util/ipcTypes.json index 452c753..5a5e3a6 100644 --- a/src/util/ipcTypes.json +++ b/src/util/ipcTypes.json @@ -1,6 +1,7 @@ { "toMain": { - "test": "toMain_test" + "test": "toMain_test", + "authStateChanged": "toMain_authStateChanged" }, "toRenderer": { "test": "toRenderer_test" diff --git a/src/util/translations/en-US/main.json b/src/util/translations/en-US/main.json new file mode 100644 index 0000000..8dba742 --- /dev/null +++ b/src/util/translations/en-US/main.json @@ -0,0 +1,5 @@ +{ + "toolbar": { + "help": "Help" + } +} diff --git a/src/util/translations/en-US/renderer.json b/src/util/translations/en-US/renderer.json new file mode 100644 index 0000000..d9f198a --- /dev/null +++ b/src/util/translations/en-US/renderer.json @@ -0,0 +1,8 @@ +{ + "translation": { + "navigation": { + "home": "Home", + "settings": "Settings" + } + } +} diff --git a/tests-examples/demo-todo-app.spec.ts b/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000..8641cb5 --- /dev/null +++ b/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,437 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +] as const; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); + }, title); +} diff --git a/tests/example.spec.ts b/tests/example.spec.ts new file mode 100644 index 0000000..54a906a --- /dev/null +++ b/tests/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects page to have a heading with the name of Installation. + await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +}); diff --git a/translations.babel b/translations.babel new file mode 100644 index 0000000..6c39f58 --- /dev/null +++ b/translations.babel @@ -0,0 +1,112 @@ + + + + i18next + translations.babel + + + + + + main + + + toolbar + + + help + false + + + + + + en-US + false + + + + + + + + + + renderer + + + translation + + + navigation + + + home + false + + + + + + en-US + false + + + + + settings + false + + + + + + en-US + false + + + + + + + + + + + + + false + + + en-US + + src/util/translations/en-US + + + + + src/util/translations/en-US + + + + true + + '%1' + { this.props.t('%1') } + { t('%1') } + + + en-US + + tab + namespaced-json + + diff --git a/tsconfig.node.json b/tsconfig.node.json index fc8ab2e..5748494 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -5,7 +5,8 @@ "src/main/**/*", "src/preload/**/*", "src/util/**/*", - "src/interfaces/**/*" + "src/interfaces/**/*", + "tests/index.spec.ts" ], "compilerOptions": { "composite": true, diff --git a/tsconfig.web.json b/tsconfig.web.json index 9c16b66..6120f9e 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -4,16 +4,15 @@ "src/renderer/src/env.d.ts", "src/renderer/src/**/*", "src/renderer/src/**/*.tsx", - "src/preload/*.d.ts" + "src/preload/*.d.ts", + "src/util/**/*" ], "compilerOptions": { "composite": true, "jsx": "react-jsx", "baseUrl": ".", "paths": { - "@renderer/*": [ - "src/renderer/src/*" - ] + "@renderer/*": ["src/renderer/src/*"] } } }