diff --git a/.github/workflows/deploy-development.yml b/.github/workflows/deploy-development.yml index 8a42942e..17531a67 100644 --- a/.github/workflows/deploy-development.yml +++ b/.github/workflows/deploy-development.yml @@ -9,6 +9,7 @@ on: jobs: build-and-deploy: runs-on: ubuntu-latest + if: github.event.pull_request.merged == true environment: DEPLOY_DEV steps: diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index c82f55f0..d08f539f 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -9,6 +9,7 @@ on: jobs: build-and-deploy: runs-on: ubuntu-latest + if: github.event.pull_request.merged == true environment: DEPLOY_PROD steps: diff --git a/package-lock.json b/package-lock.json index d7ac260f..f06519f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "axios": "^1.7.9", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-ga4": "^2.1.0", "react-icons": "^5.4.0", "react-router-dom": "^7.1.0", "vite-bundle-visualizer": "^1.2.1" @@ -562,9 +563,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", - "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", "cpu": [ "ppc64" ], @@ -579,9 +580,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", - "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", "cpu": [ "arm" ], @@ -596,9 +597,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", - "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", "cpu": [ "arm64" ], @@ -613,9 +614,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", - "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", "cpu": [ "x64" ], @@ -630,9 +631,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", - "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", "cpu": [ "arm64" ], @@ -647,9 +648,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", - "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", "cpu": [ "x64" ], @@ -664,9 +665,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", - "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", "cpu": [ "arm64" ], @@ -681,9 +682,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", - "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", "cpu": [ "x64" ], @@ -698,9 +699,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", - "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", "cpu": [ "arm" ], @@ -715,9 +716,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", - "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", "cpu": [ "arm64" ], @@ -732,9 +733,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", - "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", "cpu": [ "ia32" ], @@ -749,9 +750,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", - "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", "cpu": [ "loong64" ], @@ -766,9 +767,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", - "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", "cpu": [ "mips64el" ], @@ -783,9 +784,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", - "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", "cpu": [ "ppc64" ], @@ -800,9 +801,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", - "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", "cpu": [ "riscv64" ], @@ -817,9 +818,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", - "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", "cpu": [ "s390x" ], @@ -834,9 +835,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", - "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", "cpu": [ "x64" ], @@ -850,10 +851,27 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", - "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", "cpu": [ "x64" ], @@ -868,9 +886,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", - "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", "cpu": [ "arm64" ], @@ -885,9 +903,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", - "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", "cpu": [ "x64" ], @@ -902,9 +920,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", - "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", "cpu": [ "x64" ], @@ -919,9 +937,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", - "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", "cpu": [ "arm64" ], @@ -936,9 +954,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", - "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", "cpu": [ "ia32" ], @@ -953,9 +971,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", - "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", "cpu": [ "x64" ], @@ -1586,9 +1604,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.0.tgz", - "integrity": "sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz", + "integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==", "cpu": [ "arm" ], @@ -1600,9 +1618,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.0.tgz", - "integrity": "sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz", + "integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==", "cpu": [ "arm64" ], @@ -1614,9 +1632,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.0.tgz", - "integrity": "sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz", + "integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==", "cpu": [ "arm64" ], @@ -1628,9 +1646,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.0.tgz", - "integrity": "sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz", + "integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==", "cpu": [ "x64" ], @@ -1642,9 +1660,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.0.tgz", - "integrity": "sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz", + "integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==", "cpu": [ "arm64" ], @@ -1656,9 +1674,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.0.tgz", - "integrity": "sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz", + "integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==", "cpu": [ "x64" ], @@ -1670,9 +1688,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.0.tgz", - "integrity": "sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz", + "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==", "cpu": [ "arm" ], @@ -1684,9 +1702,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.0.tgz", - "integrity": "sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz", + "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==", "cpu": [ "arm" ], @@ -1698,9 +1716,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.0.tgz", - "integrity": "sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz", + "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==", "cpu": [ "arm64" ], @@ -1712,9 +1730,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.0.tgz", - "integrity": "sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz", + "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==", "cpu": [ "arm64" ], @@ -1725,10 +1743,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz", + "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.0.tgz", - "integrity": "sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz", + "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==", "cpu": [ "ppc64" ], @@ -1740,9 +1772,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.0.tgz", - "integrity": "sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz", + "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==", "cpu": [ "riscv64" ], @@ -1754,9 +1786,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.0.tgz", - "integrity": "sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz", + "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==", "cpu": [ "s390x" ], @@ -1768,9 +1800,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.0.tgz", - "integrity": "sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz", + "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==", "cpu": [ "x64" ], @@ -1782,9 +1814,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.0.tgz", - "integrity": "sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz", + "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==", "cpu": [ "x64" ], @@ -1796,9 +1828,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.0.tgz", - "integrity": "sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz", + "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==", "cpu": [ "arm64" ], @@ -1810,9 +1842,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.0.tgz", - "integrity": "sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz", + "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==", "cpu": [ "ia32" ], @@ -1824,9 +1856,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.0.tgz", - "integrity": "sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz", + "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==", "cpu": [ "x64" ], @@ -4883,9 +4915,9 @@ } }, "node_modules/esbuild": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", - "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4896,30 +4928,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.0", - "@esbuild/android-arm": "0.24.0", - "@esbuild/android-arm64": "0.24.0", - "@esbuild/android-x64": "0.24.0", - "@esbuild/darwin-arm64": "0.24.0", - "@esbuild/darwin-x64": "0.24.0", - "@esbuild/freebsd-arm64": "0.24.0", - "@esbuild/freebsd-x64": "0.24.0", - "@esbuild/linux-arm": "0.24.0", - "@esbuild/linux-arm64": "0.24.0", - "@esbuild/linux-ia32": "0.24.0", - "@esbuild/linux-loong64": "0.24.0", - "@esbuild/linux-mips64el": "0.24.0", - "@esbuild/linux-ppc64": "0.24.0", - "@esbuild/linux-riscv64": "0.24.0", - "@esbuild/linux-s390x": "0.24.0", - "@esbuild/linux-x64": "0.24.0", - "@esbuild/netbsd-x64": "0.24.0", - "@esbuild/openbsd-arm64": "0.24.0", - "@esbuild/openbsd-x64": "0.24.0", - "@esbuild/sunos-x64": "0.24.0", - "@esbuild/win32-arm64": "0.24.0", - "@esbuild/win32-ia32": "0.24.0", - "@esbuild/win32-x64": "0.24.0" + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" } }, "node_modules/esbuild-register": { @@ -7944,9 +7977,9 @@ } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", + "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==", "dev": true, "funding": [ { @@ -7964,7 +7997,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -8452,6 +8485,12 @@ "react": "^18.3.1" } }, + "node_modules/react-ga4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", + "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==", + "license": "MIT" + }, "node_modules/react-icons": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz", @@ -8698,9 +8737,9 @@ } }, "node_modules/rollup": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.0.tgz", - "integrity": "sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz", + "integrity": "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==", "devOptional": true, "license": "MIT", "dependencies": { @@ -8714,24 +8753,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.28.0", - "@rollup/rollup-android-arm64": "4.28.0", - "@rollup/rollup-darwin-arm64": "4.28.0", - "@rollup/rollup-darwin-x64": "4.28.0", - "@rollup/rollup-freebsd-arm64": "4.28.0", - "@rollup/rollup-freebsd-x64": "4.28.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.28.0", - "@rollup/rollup-linux-arm-musleabihf": "4.28.0", - "@rollup/rollup-linux-arm64-gnu": "4.28.0", - "@rollup/rollup-linux-arm64-musl": "4.28.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.28.0", - "@rollup/rollup-linux-riscv64-gnu": "4.28.0", - "@rollup/rollup-linux-s390x-gnu": "4.28.0", - "@rollup/rollup-linux-x64-gnu": "4.28.0", - "@rollup/rollup-linux-x64-musl": "4.28.0", - "@rollup/rollup-win32-arm64-msvc": "4.28.0", - "@rollup/rollup-win32-ia32-msvc": "4.28.0", - "@rollup/rollup-win32-x64-msvc": "4.28.0", + "@rollup/rollup-android-arm-eabi": "4.34.6", + "@rollup/rollup-android-arm64": "4.34.6", + "@rollup/rollup-darwin-arm64": "4.34.6", + "@rollup/rollup-darwin-x64": "4.34.6", + "@rollup/rollup-freebsd-arm64": "4.34.6", + "@rollup/rollup-freebsd-x64": "4.34.6", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.6", + "@rollup/rollup-linux-arm-musleabihf": "4.34.6", + "@rollup/rollup-linux-arm64-gnu": "4.34.6", + "@rollup/rollup-linux-arm64-musl": "4.34.6", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.6", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6", + "@rollup/rollup-linux-riscv64-gnu": "4.34.6", + "@rollup/rollup-linux-s390x-gnu": "4.34.6", + "@rollup/rollup-linux-x64-gnu": "4.34.6", + "@rollup/rollup-linux-x64-musl": "4.34.6", + "@rollup/rollup-win32-arm64-msvc": "4.34.6", + "@rollup/rollup-win32-ia32-msvc": "4.34.6", + "@rollup/rollup-win32-x64-msvc": "4.34.6", "fsevents": "~2.3.2" } }, @@ -10382,15 +10422,15 @@ } }, "node_modules/vite": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.3.tgz", - "integrity": "sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz", + "integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.24.0", - "postcss": "^8.4.49", - "rollup": "^4.23.0" + "esbuild": "^0.24.2", + "postcss": "^8.5.1", + "rollup": "^4.30.1" }, "bin": { "vite": "bin/vite.js" @@ -10925,9 +10965,9 @@ } }, "node_modules/vite-node/node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, "license": "MIT", "dependencies": { @@ -11547,9 +11587,9 @@ } }, "node_modules/vitest/node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index a5bb4633..6c2affc1 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "axios": "^1.7.9", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-ga4": "^2.1.0", "react-icons": "^5.4.0", "react-router-dom": "^7.1.0", "vite-bundle-visualizer": "^1.2.1" diff --git a/src/apis/apis.ts b/src/apis/apis.ts index c4e6cd3a..ab13465a 100644 --- a/src/apis/apis.ts +++ b/src/apis/apis.ts @@ -7,7 +7,7 @@ import { PostUserResponseType, PutDebateTableResponseType, } from './responseTypes'; -import { DebateInfo } from '../type/type'; +import { DetailDebateInfo, TimeBoxInfo } from '../type/type'; import { setAccessToken } from '../util/accessToken'; // String type identifier for TanStack Query's 'useQuery' function @@ -68,11 +68,8 @@ export async function getParliamentaryTableData( // POST /api/table/parliamentary export async function postParliamentaryDebateTable( - tableName: string, - tableAgenda: string, - warningBell: boolean, - finishBell: boolean, - tables: DebateInfo[], + info: DetailDebateInfo, + tables: TimeBoxInfo[], ): Promise { const requestUrl: string = ApiUrl.parliamentary; const response = await request( @@ -80,10 +77,10 @@ export async function postParliamentaryDebateTable( requestUrl, { info: { - name: tableName === '' ? '테이블 1' : tableName, - agenda: tableAgenda, - warningBell: warningBell, - finishBell: finishBell, + name: info.name === '' ? '테이블 1' : info.name, + agenda: info.agenda, + warningBell: info.warningBell, + finishBell: info.finishBell, }, table: tables, }, @@ -96,11 +93,8 @@ export async function postParliamentaryDebateTable( // PUT /api/table/parliamentary/{tableId} export async function putParliamentaryDebateTable( tableId: number, - tableName: string, - tableAgenda: string, - warningBell: boolean, - finishBell: boolean, - tables: DebateInfo[], + info: DetailDebateInfo, + tables: TimeBoxInfo[], ): Promise { const requestUrl: string = ApiUrl.parliamentary; const response = await request( @@ -108,10 +102,10 @@ export async function putParliamentaryDebateTable( requestUrl + `/${tableId}`, { info: { - name: tableName, - agenda: tableAgenda, - warningBell: warningBell, - finishBell: finishBell, + name: info.name, + agenda: info.agenda, + warningBell: info.warningBell, + finishBell: info.finishBell, }, table: tables, }, diff --git a/src/apis/primitives.ts b/src/apis/primitives.ts index cba6468a..9ad24167 100644 --- a/src/apis/primitives.ts +++ b/src/apis/primitives.ts @@ -13,11 +13,11 @@ export async function request( data: object | null, params: object | null, ): Promise> { - // console.log(`# endpoint = ${endpoint}`); + const instance = axiosInstance; try { // Get response - const response: AxiosResponse = await axiosInstance({ + const response: AxiosResponse = await instance({ method, url: endpoint, data: data ? JSON.stringify(data) : null, diff --git a/src/apis/responseTypes.ts b/src/apis/responseTypes.ts index 8f72545d..b3026971 100644 --- a/src/apis/responseTypes.ts +++ b/src/apis/responseTypes.ts @@ -1,4 +1,4 @@ -import { DebateInfo, DebateTable } from '../type/type'; +import { TimeBoxInfo, DebateTable, DetailDebateInfo } from '../type/type'; // POST "/api/member" export interface PostUserResponseType { @@ -14,37 +14,22 @@ export interface GetDebateTableListResponseType { // GET /api/table/parliamentary/{tableId} export interface GetTableDataResponseType { id: number; - info: { - name: string; - agenda: string; - warningBell: boolean; - finishBell: boolean; - }; - table: DebateInfo[]; + info: DetailDebateInfo; + table: TimeBoxInfo[]; } // POST /api/table/parliamentary export interface PostDebateTableResponseType { id: number; - info: { - name: string; - agenda: string; - warningBell: boolean; - finishBell: boolean; - }; - table: DebateInfo[]; + info: DetailDebateInfo; + table: TimeBoxInfo[]; } // PUT /api/table/parliamentary/{tableId} export interface PutDebateTableResponseType { id: number; - info: { - name: string; - agenda: string; - warningBell: boolean; - finishBell: boolean; - }; - table: DebateInfo[]; + info: DetailDebateInfo; + table: TimeBoxInfo[]; } // DELETE /api/table/parliamentary/{tableId} diff --git a/src/components/GoogleButton.tsx b/src/components/GoogleButton.tsx index 27d01068..dec5eb9c 100644 --- a/src/components/GoogleButton.tsx +++ b/src/components/GoogleButton.tsx @@ -1,19 +1,46 @@ import { ButtonHTMLAttributes } from 'react'; import { FcGoogle } from 'react-icons/fc'; +import { isEmbeddedWebView } from '../util/validateUserAgent'; +import { MdOutlineErrorOutline } from 'react-icons/md'; export default function GoogleButton( props: ButtonHTMLAttributes, ) { + // Check whether user-agent is acceptable and set background color + const isDisabled = isEmbeddedWebView(); + const bgColor = isDisabled ? 'bg-gray-300' : 'bg-slate-100'; + const hoverBgColor = isDisabled ? '' : 'hover:bg-slate-200'; + console.log(isDisabled); + return ( - + rounded-full ${bgColor} px-8 py-2 text-sm shadow-lg ${hoverBgColor} + `} + disabled={isDisabled} + > +
+ + + Google 계정으로 로그인 + +
+ + + {/* Error message */} + {isDisabled && ( +
+ +

+ 이 브라우저에서는 로그인이 불가능해요. 다른 웹 브라우저로 + 접속해주세요. +

+
+ )} + ); } diff --git a/src/constants/languageMapping.ts b/src/constants/languageMapping.ts index 433913a4..7a0fe006 100644 --- a/src/constants/languageMapping.ts +++ b/src/constants/languageMapping.ts @@ -2,5 +2,4 @@ import { Type } from '../type/type'; export const typeMapping: Record = { PARLIAMENTARY: '의회식 토론', - TIMEBASED: '시간 총량제 토론', }; diff --git a/src/hooks/mutations/useAddTable.ts b/src/hooks/mutations/useAddTable.ts index 5e014540..b31ea594 100644 --- a/src/hooks/mutations/useAddTable.ts +++ b/src/hooks/mutations/useAddTable.ts @@ -1,24 +1,23 @@ import { useMutation } from '@tanstack/react-query'; import { postParliamentaryDebateTable } from '../../apis/apis'; -import { DebateInfo } from '../../type/type'; +import { DetailDebateInfo, TimeBoxInfo } from '../../type/type'; import { PostDebateTableResponseType } from '../../apis/responseTypes'; interface UseAddTableParams { - tableName: string; - tableAgenda: string; - warningBell: boolean; - finishBell: boolean; - table: DebateInfo[]; + info: DetailDebateInfo; + table: TimeBoxInfo[]; } export default function useAddTable(onSuccess: (id: number) => void) { return useMutation({ mutationFn: async (params: UseAddTableParams) => { const response = await postParliamentaryDebateTable( - params.tableName, - params.tableAgenda, - params.warningBell, - params.finishBell, + { + name: params.info.name, + agenda: params.info.agenda, + warningBell: params.info.warningBell, + finishBell: params.info.finishBell, + }, params.table, ); return response; diff --git a/src/hooks/mutations/usePutParliamentaryDebateTable.ts b/src/hooks/mutations/usePutParliamentaryDebateTable.ts index 2e6bc047..c6bfda3a 100644 --- a/src/hooks/mutations/usePutParliamentaryDebateTable.ts +++ b/src/hooks/mutations/usePutParliamentaryDebateTable.ts @@ -1,15 +1,12 @@ import { useMutation } from '@tanstack/react-query'; import { putParliamentaryDebateTable } from '../../apis/apis'; import { PutDebateTableResponseType } from '../../apis/responseTypes'; -import { DebateInfo } from '../../type/type'; +import { DetailDebateInfo, TimeBoxInfo } from '../../type/type'; interface PutParliamentaryTableParams { tableId: number; - tableName: string; - tableAgenda: string; - warningBell: boolean; - finishBell: boolean; - table: DebateInfo[]; + info: DetailDebateInfo; + table: TimeBoxInfo[]; } export function usePutParliamentaryDebateTable( @@ -20,22 +17,8 @@ export function usePutParliamentaryDebateTable( Error, PutParliamentaryTableParams >({ - mutationFn: ({ - tableId, - tableName, - tableAgenda, - table, - warningBell, - finishBell, - }) => - putParliamentaryDebateTable( - tableId, - tableName, - tableAgenda, - warningBell, - finishBell, - table, - ), + mutationFn: ({ tableId, info, table }) => + putParliamentaryDebateTable(tableId, info, table), onSuccess: (response: PutDebateTableResponseType) => { onSuccess(response.id); }, diff --git a/src/main.tsx b/src/main.tsx index 9b3e83b0..e5ba933b 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,6 +5,7 @@ import { GlobalPortal } from './util/GlobalPortal'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import router from './routes/routes.tsx'; import './index.css'; +import { setupGoogleAnalytics } from './util/setupGoogleAnalytics.tsx'; // console.log(`# URL = ${import.meta.env.VITE_API_BASE_URL}`); @@ -41,6 +42,8 @@ if (import.meta.env.VITE_MOCK_API === 'true') { // Function that initializes main React app function initializeApp() { + setupGoogleAnalytics(); + // Call queryClient for TanStack Query const queryClient = new QueryClient({ defaultOptions: { diff --git a/src/page/LoginPage/LoginPage.test.tsx b/src/page/LoginPage/LoginPage.test.tsx index 0c535875..f1a82b00 100644 --- a/src/page/LoginPage/LoginPage.test.tsx +++ b/src/page/LoginPage/LoginPage.test.tsx @@ -36,6 +36,6 @@ describe('LoginPage', () => { expect(screen.getByText('Debate Timer')).toBeInTheDocument(); // 입력 필드와 버튼 확인 - expect(screen.getByText('구글 계정으로 로그인')).toBeInTheDocument(); + expect(screen.getByText('Google 계정으로 로그인')).toBeInTheDocument(); }); }); diff --git a/src/page/TableComposition/TableComposition.tsx b/src/page/TableComposition/TableComposition.tsx index a9a68a42..70816ae4 100644 --- a/src/page/TableComposition/TableComposition.tsx +++ b/src/page/TableComposition/TableComposition.tsx @@ -33,6 +33,8 @@ export default function TableComposition() { name: fetchedTableData.info.name, agenda: fetchedTableData.info.agenda, type: type, + warningBell: fetchedTableData.info.warningBell, + finishBell: fetchedTableData.info.finishBell, }, table: fetchedTableData.table, }; @@ -47,18 +49,22 @@ export default function TableComposition() { if (mode === 'edit') { EditTable({ tableId: tableId, // etc - tableName: formData.info.name ?? '테이블 1', - tableAgenda: formData.info.agenda, - warningBell: true, - finishBell: true, + info: { + name: formData.info.name ?? '테이블 1', + agenda: formData.info.agenda, + warningBell: formData.info.warningBell, + finishBell: formData.info.finishBell, + }, table: formData.table, }); } else { AddTable({ - tableName: formData.info.name ?? '테이블 1', - tableAgenda: formData.info.agenda, - warningBell: true, - finishBell: true, + info: { + name: formData.info.name ?? '테이블 1', + agenda: formData.info.agenda, + warningBell: formData.info.warningBell, + finishBell: formData.info.finishBell, + }, table: formData.table, }); } @@ -72,7 +78,7 @@ export default function TableComposition() { goNextStep('TimeBox')} /> ), @@ -80,7 +86,6 @@ export default function TableComposition() { diff --git a/src/page/TableComposition/components/DebatePanel/DebatePanel.tsx b/src/page/TableComposition/components/DebatePanel/DebatePanel.tsx index 7e6a3ebf..aaf638d9 100644 --- a/src/page/TableComposition/components/DebatePanel/DebatePanel.tsx +++ b/src/page/TableComposition/components/DebatePanel/DebatePanel.tsx @@ -1,11 +1,11 @@ import { HTMLAttributes } from 'react'; import EditDeleteButtons from '../EditDeleteButtons/EditDeleteButtons'; -import { DebateInfo, DebateTypeToString } from '../../../../type/type'; +import { TimeBoxInfo, DebateTypeToString } from '../../../../type/type'; import { Formatting } from '../../../../util/formatting'; import { LuArrowUpDown } from 'react-icons/lu'; interface DebatePanelProps extends HTMLAttributes { - info: DebateInfo; - onSubmitEdit?: (updatedInfo: DebateInfo) => void; + info: TimeBoxInfo; + onSubmitEdit?: (updatedInfo: TimeBoxInfo) => void; onSubmitDelete?: () => void; } diff --git a/src/page/TableComposition/components/DropdownForDebateType/DropdownForDebateType.tsx b/src/page/TableComposition/components/DropdownForDebateType/DropdownForDebateType.tsx index d2e0bd45..a61ae2f7 100644 --- a/src/page/TableComposition/components/DropdownForDebateType/DropdownForDebateType.tsx +++ b/src/page/TableComposition/components/DropdownForDebateType/DropdownForDebateType.tsx @@ -31,7 +31,7 @@ export default function DropdownForDebateType( ); return ( -
+
- + + + + -
-
-

토론 시간표 이름

- -
+
+

토론 템플릿 이름

+ + +

토론 주제

+ {!isEdit && ( -
+ <>

토론 유형

-
+ )} + +

종소리 설정

+
+ + +
+ diff --git a/src/page/TableComposition/components/TimerCreationContent/TimerCreationContent.tsx b/src/page/TableComposition/components/TimerCreationContent/TimerCreationContent.tsx index 1f0014b3..686bc02c 100644 --- a/src/page/TableComposition/components/TimerCreationContent/TimerCreationContent.tsx +++ b/src/page/TableComposition/components/TimerCreationContent/TimerCreationContent.tsx @@ -1,11 +1,11 @@ import { useState } from 'react'; -import { DebateInfo, DebateType, Stance } from '../../../../type/type'; +import { TimeBoxInfo, DebateType, Stance } from '../../../../type/type'; import { Formatting } from '../../../../util/formatting'; interface TimerCreationContentProps { selectedStance: Stance; - initDate?: DebateInfo; - onSubmit: (data: DebateInfo) => void; + initDate?: TimeBoxInfo; + onSubmit: (data: TimeBoxInfo) => void; onClose: () => void; // 모달 닫기 함수 } @@ -17,7 +17,7 @@ export default function TimerCreationContent({ }: TimerCreationContentProps) { const [stance, setStance] = useState(selectedStance); const [debateType, setDebateType] = useState( - initDate?.stance === 'NEUTRAL' ? 'OPENING' : (initDate?.type ?? 'OPENING'), + initDate?.type ?? 'OPENING', ); const { minutes: initMinutes, seconds: initSeconds } = Formatting.formatSecondsToMinutes(initDate?.time ?? 180); @@ -89,7 +89,9 @@ export default function TimerCreationContent({ if (e.target.value === 'TIME_OUT') { setStance('NEUTRAL'); } else { - setStance(selectedStance); + setStance( + selectedStance === 'NEUTRAL' ? 'CONS' : selectedStance, + ); } setDebateType(e.target.value as DebateType); }} diff --git a/src/page/TableComposition/hook/useTableFrom.tsx b/src/page/TableComposition/hook/useTableFrom.tsx index 0b6ea9db..20dffe77 100644 --- a/src/page/TableComposition/hook/useTableFrom.tsx +++ b/src/page/TableComposition/hook/useTableFrom.tsx @@ -2,17 +2,13 @@ import { useEffect } from 'react'; import { useNavigate, useNavigationType } from 'react-router-dom'; import { TableCompositionStep } from '../TableComposition'; import useBrowserStorage from '../../../hooks/useBrowserStorage'; -import { DebateInfo, Type } from '../../../type/type'; +import { DetailDebateInfo, TimeBoxInfo, Type } from '../../../type/type'; import useAddTable from '../../../hooks/mutations/useAddTable'; import { usePutParliamentaryDebateTable } from '../../../hooks/mutations/usePutParliamentaryDebateTable'; export interface TableFormData { - info: { - name: string; - agenda: string; - type: Type; // 새로 추가된 속성 - }; - table: DebateInfo[]; + info: DetailDebateInfo & { type: Type }; + table: TimeBoxInfo[]; } const useTableFrom = ( currentStep: TableCompositionStep, @@ -29,6 +25,8 @@ const useTableFrom = ( name: '', agenda: '', type: 'PARLIAMENTARY', + warningBell: true, + finishBell: true, }, table: [], }, @@ -57,10 +55,22 @@ const useTableFrom = ( }, [currentStep, navigationType, navigate]); const updateInfo: React.Dispatch< - React.SetStateAction<{ name: string; agenda: string; type: Type }> + React.SetStateAction<{ + name: string; + agenda: string; + type: Type; + warningBell: boolean; + finishBell: boolean; + }> > = (action) => { setFormData((prev) => { - let newInfo: { name: string; agenda: string; type: Type }; + let newInfo: { + name: string; + agenda: string; + type: Type; + warningBell: boolean; + finishBell: boolean; + }; if (typeof action === 'function') { newInfo = (action as (arg: typeof prev.info) => typeof prev.info)( prev.info, @@ -72,13 +82,15 @@ const useTableFrom = ( }); }; - const updateTable: React.Dispatch> = ( + const updateTable: React.Dispatch> = ( action, ) => { setFormData((prev) => { - let newTable: DebateInfo[]; + let newTable: TimeBoxInfo[]; if (typeof action === 'function') { - newTable = (action as (arg: DebateInfo[]) => DebateInfo[])(prev.table); + newTable = (action as (arg: TimeBoxInfo[]) => TimeBoxInfo[])( + prev.table, + ); } else { newTable = action; } diff --git a/src/page/TableOverviewPage/TableOverview.stories.tsx b/src/page/TableOverviewPage/TableOverview.stories.tsx index e3c2c21b..bf0c041a 100644 --- a/src/page/TableOverviewPage/TableOverview.stories.tsx +++ b/src/page/TableOverviewPage/TableOverview.stories.tsx @@ -1,6 +1,6 @@ import { Meta, StoryObj } from '@storybook/react'; import TableOverview from './TableOverview'; -import { DebateInfo } from '../../type/type'; +import { TimeBoxInfo } from '../../type/type'; // 1) 메타 설정 const meta: Meta = { @@ -28,6 +28,6 @@ export const Default: Story = { time: 180, speakerNumber: 2, }, - ] as DebateInfo[], + ] as TimeBoxInfo[], }, }; diff --git a/src/page/TableOverviewPage/TableOverview.tsx b/src/page/TableOverviewPage/TableOverview.tsx index 3fdedabb..130c4bd1 100644 --- a/src/page/TableOverviewPage/TableOverview.tsx +++ b/src/page/TableOverviewPage/TableOverview.tsx @@ -31,7 +31,7 @@ export default function TableOverview() {

토론 주제

-

+

{data === undefined || data!.info.agenda.trim() === '' ? '주제 없음' : data!.info.agenda} diff --git a/src/page/TimerPage/TimerPage.tsx b/src/page/TimerPage/TimerPage.tsx index 0d8352f5..01f39dac 100644 --- a/src/page/TimerPage/TimerPage.tsx +++ b/src/page/TimerPage/TimerPage.tsx @@ -1,41 +1,27 @@ -import { useState, useRef, useEffect, useCallback } from 'react'; -import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; -import TimerComponent from './components/Timer/TimerComponent'; -import DebateInfoSummary from './components/DebateInfoSummary'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import TimerLoadingPage from './TimerLoadingPage'; +import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; +import useLogout from '../../hooks/mutations/useLogout'; +import HeaderButtons from './components/common/HeaderButtons'; import { useGetParliamentaryTableData } from '../../hooks/query/useGetParliamentaryTableData'; +import TimerLoadingPage from './TimerLoadingPage'; import { useModal } from '../../hooks/useModal'; -import AdditionalTimerComponent from './components/AdditionalTimer/AdditionalTimerComponent'; -import { IoMdHome } from 'react-icons/io'; -import useLogout from '../../hooks/mutations/useLogout'; import { useTimer } from './hooks/useTimer'; +import TimerComponent from './components/Timer/TimerComponent'; +import TimerTimeTable from './components/Timer/TimerTimeTable'; import FirstUseToolTip from './components/common/FirstUseToolTip'; import useMobile from '../../hooks/useMobile'; -import { IoHelpCircle } from 'react-icons/io5'; +import AdditionalTimerComponent from './components/AdditionalTimer/AdditionalTimerComponent'; export default function TimerPage() { // Load sounds const dingOnceRef = useRef(null); const dingTwiceRef = useRef(null); - const navigate = useNavigate(); - // Prepare data before requesting query + // Prepare parameter const pathParams = useParams(); const tableId = pathParams.id; - // Validate parameters is prepared - if (tableId === undefined) { - throw new Error("Failed to resolve 'tableId' from request URL"); - } - - // Prepare for modal - const { isOpen, openModal, ModalWrapper } = useModal(); - - // Get query - const { data, isLoading } = useGetParliamentaryTableData(Number(tableId)); - const { mutate: logoutMutate } = useLogout(() => navigate('/login')); - // Use timer hook const { timer, @@ -43,19 +29,27 @@ export default function TimerPage() { pauseTimer, startTimer, isRunning, - actOnTime, resetTimer, setDefaultValue, } = useTimer(); - // Declare states - const [index, setIndex] = useState(0); - const [sfxFlag, setSfxFlag] = useState(0); // (30s, 0s) / (XX) = 0, (XO) = 1, (OX) = 2, (OO) = 3 + // Get query + const { data, isLoading } = useGetParliamentaryTableData(Number(tableId)); + + // Set states const [isFirst, setIsFirst] = useState(false); + const [index, setIndex] = useState(0); const [bg, setBg] = useState(''); + const [isWarningBellOn, setWarningBell] = useState(false); + const [isFinishBellOn, setFinishBell] = useState(false); + + // Prepare other hooks const isMobile = useMobile(); + const navigate = useNavigate(); + const { isOpen, openModal, ModalWrapper } = useModal(); + const { mutate: logoutMutate } = useLogout(() => navigate('/login')); - // Declare function to manage parent component's index + // Declare functions const moveToOtherItem = useCallback( (goToPrev: boolean) => { if (goToPrev) { @@ -72,8 +66,20 @@ export default function TimerPage() { [data, index, resetTimer], ); - const changeBg = (condition: boolean, timer: number) => { - if (condition) { + // Open tooltip when value of 'isFirst' is true + useEffect(() => { + const storedIsFirst = localStorage.getItem('isFirst'); + + if (storedIsFirst === null) { + setIsFirst(true); + } else { + setIsFirst(storedIsFirst.trim() === 'true' ? true : false); + } + }, []); + + // Change background color + useEffect(() => { + if (isRunning) { if (timer > 30) { setBg('gradient-timer-running'); } else if (timer >= 0 && timer <= 30) { @@ -84,12 +90,28 @@ export default function TimerPage() { } else { setBg(''); } - }; + }, [isRunning, timer]); - // Set parent component's background animation by timer's state and remaining time + // Play bells useEffect(() => { - changeBg(isRunning, timer); - }, [timer, isRunning]); + if (dingOnceRef.current && isRunning && isWarningBellOn && timer === 30) { + dingOnceRef.current.play(); + } + + if (dingTwiceRef.current && isRunning && isFinishBellOn && timer === 0) { + dingTwiceRef.current.play(); + } + }, [timer, isFinishBellOn, isWarningBellOn, isRunning]); + + // Initiate timer + useEffect(() => { + if (data) { + setDefaultValue(data.table[index].time); + setTimer(data.table[index].time); + setWarningBell(data.info.warningBell); + setFinishBell(data.info.finishBell); + } + }, [data, index, setDefaultValue, setTimer]); // Add keyboard event listener useEffect(() => { @@ -106,103 +128,43 @@ export default function TimerPage() { switch (event.code) { case 'Space': if (isRunning) { - // console.log('# timer paused'); pauseTimer(); - changeBg(isRunning, timer); } else { - // console.log('# timer started'); startTimer(); - changeBg(isRunning, timer); } break; case 'ArrowLeft': moveToOtherItem(true); - changeBg(isRunning, timer); break; case 'ArrowRight': moveToOtherItem(false); - changeBg(isRunning, timer); break; case 'KeyR': resetTimer(); - changeBg(isRunning, timer); break; } }; - // Set listener when component is rendered window.addEventListener('keydown', handleKeyDown); return () => { // Remove listener when component is rendered window.removeEventListener('keydown', handleKeyDown); }; - }, [ - isRunning, - isOpen, - moveToOtherItem, - pauseTimer, - resetTimer, - startTimer, - timer, - ]); - - // Let timer play sounds when o nly 30 seconds left or timeout - useEffect(() => { - actOnTime(30, () => { - if (dingOnceRef.current && isRunning) { - dingOnceRef.current.play(); - } - }); - - actOnTime(0, () => { - if (dingTwiceRef.current && isRunning) { - dingTwiceRef.current.play(); - } - }); - }, [actOnTime, isRunning]); - - // Let timer initialize itself when data is loaded via api - useEffect(() => { - if (data) { - setDefaultValue(data.table[index].time); - setTimer(data.table[index].time); - - if (data.info.warningBell && data.info.finishBell) { - setSfxFlag(3); - } else if (data.info.warningBell && !data.info.finishBell) { - setSfxFlag(2); - } else if (!data.info.warningBell && data.info.finishBell) { - setSfxFlag(1); - } else { - setSfxFlag(0); - } - } - }, [data, index, setDefaultValue, setTimer]); + }, [isOpen, isRunning, resetTimer, startTimer, moveToOtherItem, pauseTimer]); - useEffect(() => { - const storedIsFirst = localStorage.getItem('isFirst'); - if (storedIsFirst) { - setIsFirst(storedIsFirst.trim() === 'true' ? true : false); - } - }, []); - - // Handle exceptions + // Print loading page if (isLoading) { - // TODO: HAVE TO CLEARED - setSfxFlag(sfxFlag); return ; } - // console.log(`# index = ${index}, data = ` + data!.table[index].time); - // console.log(`# isRunning = ${isRunning}`); - - // Return React component + // Return TimerPage return ( <>