diff --git a/package-lock.json b/package-lock.json
index 19b9ff3..5368607 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,7 +18,7 @@
"@tiptap/react": "2.9.1",
"@tiptap/starter-kit": "2.9.1",
"@types/react-helmet": "^6.1.11",
- "axios": "1.7.3",
+ "axios": "^1.7.9",
"bech32": "2.0.0",
"buffer": "6.0.3",
"date-fns": "3.6.0",
@@ -1206,208 +1206,266 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz",
- "integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz",
+ "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz",
- "integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz",
+ "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz",
- "integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz",
+ "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz",
- "integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz",
+ "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz",
+ "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz",
+ "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz",
- "integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz",
+ "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz",
- "integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz",
+ "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz",
- "integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz",
+ "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz",
- "integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz",
+ "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz",
+ "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz",
- "integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz",
+ "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==",
"cpu": [
"ppc64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz",
- "integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz",
+ "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==",
"cpu": [
"riscv64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz",
- "integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz",
+ "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==",
"cpu": [
"s390x"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz",
- "integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz",
+ "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz",
- "integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz",
+ "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz",
- "integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz",
+ "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz",
- "integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz",
+ "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==",
"cpu": [
"ia32"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz",
- "integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz",
+ "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -1993,10 +2051,11 @@
}
},
"node_modules/@types/estree": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
- "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
- "dev": true
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@types/file-saver": {
"version": "2.0.7",
@@ -2434,9 +2493,10 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
- "version": "1.7.3",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz",
- "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==",
+ "version": "1.7.9",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
+ "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
+ "license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
@@ -2719,10 +2779,11 @@
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
},
"node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -4359,10 +4420,11 @@
}
},
"node_modules/picocolors": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
- "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
- "dev": true
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -4377,9 +4439,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.39",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
- "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"dev": true,
"funding": [
{
@@ -4395,10 +4457,11 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
- "picocolors": "^1.0.1",
- "source-map-js": "^1.2.0"
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -4912,12 +4975,13 @@
}
},
"node_modules/rollup": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz",
- "integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz",
+ "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@types/estree": "1.0.5"
+ "@types/estree": "1.0.6"
},
"bin": {
"rollup": "dist/bin/rollup"
@@ -4927,22 +4991,25 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.18.1",
- "@rollup/rollup-android-arm64": "4.18.1",
- "@rollup/rollup-darwin-arm64": "4.18.1",
- "@rollup/rollup-darwin-x64": "4.18.1",
- "@rollup/rollup-linux-arm-gnueabihf": "4.18.1",
- "@rollup/rollup-linux-arm-musleabihf": "4.18.1",
- "@rollup/rollup-linux-arm64-gnu": "4.18.1",
- "@rollup/rollup-linux-arm64-musl": "4.18.1",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.18.1",
- "@rollup/rollup-linux-riscv64-gnu": "4.18.1",
- "@rollup/rollup-linux-s390x-gnu": "4.18.1",
- "@rollup/rollup-linux-x64-gnu": "4.18.1",
- "@rollup/rollup-linux-x64-musl": "4.18.1",
- "@rollup/rollup-win32-arm64-msvc": "4.18.1",
- "@rollup/rollup-win32-ia32-msvc": "4.18.1",
- "@rollup/rollup-win32-x64-msvc": "4.18.1",
+ "@rollup/rollup-android-arm-eabi": "4.28.1",
+ "@rollup/rollup-android-arm64": "4.28.1",
+ "@rollup/rollup-darwin-arm64": "4.28.1",
+ "@rollup/rollup-darwin-x64": "4.28.1",
+ "@rollup/rollup-freebsd-arm64": "4.28.1",
+ "@rollup/rollup-freebsd-x64": "4.28.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.28.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.28.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.28.1",
+ "@rollup/rollup-linux-arm64-musl": "4.28.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.28.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.28.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.28.1",
+ "@rollup/rollup-linux-x64-gnu": "4.28.1",
+ "@rollup/rollup-linux-x64-musl": "4.28.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.28.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.28.1",
+ "@rollup/rollup-win32-x64-msvc": "4.28.1",
"fsevents": "~2.3.2"
}
},
@@ -5042,10 +5109,11 @@
}
},
"node_modules/source-map-js": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
- "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
+ "license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
@@ -5397,14 +5465,15 @@
"dev": true
},
"node_modules/vite": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.3.tgz",
- "integrity": "sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==",
+ "version": "5.4.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
+ "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",
- "postcss": "^8.4.39",
- "rollup": "^4.13.0"
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
@@ -5423,6 +5492,7 @@
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
+ "sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
@@ -5440,6 +5510,9 @@
"sass": {
"optional": true
},
+ "sass-embedded": {
+ "optional": true
+ },
"stylus": {
"optional": true
},
diff --git a/package.json b/package.json
index b356c8a..a99abbb 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
"@tiptap/react": "2.9.1",
"@tiptap/starter-kit": "2.9.1",
"@types/react-helmet": "^6.1.11",
- "axios": "1.7.3",
+ "axios": "^1.7.9",
"bech32": "2.0.0",
"buffer": "6.0.3",
"date-fns": "3.6.0",
diff --git a/src/assets/categories/categories.json b/src/assets/categories/categories.json
new file mode 100644
index 0000000..eaa4079
--- /dev/null
+++ b/src/assets/categories/categories.json
@@ -0,0 +1,27 @@
+[
+ {
+ "name": "audio",
+ "sub": [
+ { "name": "music", "sub": ["background", "ambient"] },
+ {
+ "name": "sound effects",
+ "sub": ["footsteps", "weapons"]
+ },
+ "voice"
+ ]
+ },
+ {
+ "name": "graphical",
+ "sub": [
+ {
+ "name": "textures",
+ "sub": ["highres textures", "lowres textures"]
+ },
+ "models",
+ "shaders"
+ ]
+ },
+ { "name": "user interface", "sub": ["hud", "menus", "icons"] },
+ { "name": "gameplay", "sub": ["mechanics", "balance", "ai"] },
+ "bugfixes"
+]
diff --git a/src/components/AlertPopup.tsx b/src/components/AlertPopup.tsx
new file mode 100644
index 0000000..e9334ee
--- /dev/null
+++ b/src/components/AlertPopup.tsx
@@ -0,0 +1,72 @@
+import { createPortal } from 'react-dom'
+import { AlertPopupProps } from 'types'
+
+export const AlertPopup = ({
+ header,
+ label,
+ handleConfirm,
+ handleClose
+}: AlertPopupProps) => {
+ return createPortal(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
,
+ document.body
+ )
+}
diff --git a/src/components/CategoryAutocomplete.tsx b/src/components/CategoryAutocomplete.tsx
new file mode 100644
index 0000000..4c2ea50
--- /dev/null
+++ b/src/components/CategoryAutocomplete.tsx
@@ -0,0 +1,333 @@
+import { useLocalStorage } from 'hooks'
+import { useMemo, useState } from 'react'
+import { Link } from 'react-router-dom'
+import { getGamePageRoute } from 'routes'
+import { ModFormState, Categories, Category } from 'types'
+import {
+ getCategories,
+ flattenCategories,
+ addToUserCategories,
+ capitalizeEachWord
+} from 'utils'
+
+interface CategoryAutocompleteProps {
+ game: string
+ LTags: string[]
+ setFormState: (value: React.SetStateAction) => void
+}
+
+export const CategoryAutocomplete = ({
+ game,
+ LTags,
+ setFormState
+}: CategoryAutocompleteProps) => {
+ // Fetch the hardcoded categories from assets
+ const flattenedCategories = useMemo(() => getCategories(), [])
+
+ // Fetch the user categories from local storage
+ const [userHierarchies, setUserHierarchies] = useLocalStorage<
+ (string | Category)[]
+ >('user-hierarchies', [])
+ const flattenedUserCategories = useMemo(
+ () => flattenCategories(userHierarchies, []),
+ [userHierarchies]
+ )
+
+ // Create options and select categories from the mod LTags (hierarchies)
+ const { selectedCategories, combinedOptions } = useMemo(() => {
+ const combinedCategories = [
+ ...flattenedCategories,
+ ...flattenedUserCategories
+ ]
+ const hierarchies = LTags.map((hierarchy) => {
+ const existingCategory = combinedCategories.find(
+ (cat) => cat.hierarchy === hierarchy.replace(/:/g, ' > ')
+ )
+ if (existingCategory) {
+ return existingCategory
+ } else {
+ const segments = hierarchy.split(':')
+ const lastSegment = segments[segments.length - 1]
+ return { name: lastSegment, hierarchy: hierarchy, l: [lastSegment] }
+ }
+ })
+
+ // Selected categorires (based on the LTags)
+ const selectedCategories = Array.from(new Set([...hierarchies]))
+
+ // Combine user, predefined category hierarchies and selected values (LTags in case some are missing)
+ const combinedOptions = Array.from(
+ new Set([...combinedCategories, ...selectedCategories])
+ )
+
+ return { selectedCategories, combinedOptions }
+ }, [LTags, flattenedCategories, flattenedUserCategories])
+
+ const [inputValue, setInputValue] = useState('')
+ const filteredOptions = useMemo(
+ () =>
+ combinedOptions.filter((option) =>
+ option.hierarchy.toLowerCase().includes(inputValue.toLowerCase())
+ ),
+ [combinedOptions, inputValue]
+ )
+
+ const getSelectedCategories = (cats: Categories[]) => {
+ const uniqueValues = new Set(
+ cats.reduce((prev, cat) => [...prev, ...cat.l], [])
+ )
+ const concatenatedValue = Array.from(uniqueValues)
+ return concatenatedValue
+ }
+ const getSelectedHierarchy = (cats: Categories[]) => {
+ const hierarchies = cats.reduce(
+ (prev, cat) => [...prev, cat.hierarchy.replace(/ > /g, ':')],
+ []
+ )
+ const concatenatedValue = Array.from(hierarchies)
+ return concatenatedValue
+ }
+ const handleReset = () => {
+ setFormState((prevState) => ({
+ ...prevState,
+ ['lTags']: [],
+ ['LTags']: []
+ }))
+ setInputValue('')
+ }
+ const handleRemove = (option: Categories) => {
+ const updatedCategories = selectedCategories.filter(
+ (cat) => cat.hierarchy !== option.hierarchy
+ )
+ setFormState((prevState) => ({
+ ...prevState,
+ ['lTags']: getSelectedCategories(updatedCategories),
+ ['LTags']: getSelectedHierarchy(updatedCategories)
+ }))
+ }
+ const handleSelect = (option: Categories) => {
+ if (!selectedCategories.some((cat) => cat.hierarchy === option.hierarchy)) {
+ const updatedCategories = [...selectedCategories, option]
+ setFormState((prevState) => ({
+ ...prevState,
+ ['lTags']: getSelectedCategories(updatedCategories),
+ ['LTags']: getSelectedHierarchy(updatedCategories)
+ }))
+ }
+ setInputValue('')
+ }
+ const handleInputChange = (e: React.ChangeEvent) => {
+ setInputValue(e.target.value)
+ }
+ const handleAddNew = () => {
+ if (inputValue) {
+ const value = inputValue.trim().toLowerCase()
+ const values = value.split('>').map((s) => s.trim())
+ const newOption: Categories = {
+ name: value,
+ hierarchy: value,
+ l: values
+ }
+ setUserHierarchies((prev) => {
+ addToUserCategories(prev, value)
+ return [...prev]
+ })
+ const updatedCategories = [...selectedCategories, newOption]
+ setFormState((prevState) => ({
+ ...prevState,
+ ['lTags']: getSelectedCategories(updatedCategories),
+ ['LTags']: getSelectedHierarchy(updatedCategories)
+ }))
+ setInputValue('')
+ }
+ }
+ const handleAddNewCustom = (option: Categories) => {
+ setUserHierarchies((prev) => {
+ addToUserCategories(prev, option.hierarchy)
+ return [...prev]
+ })
+ }
+
+ const Row = ({ index }: { index: number }) => {
+ return (
+ handleSelect(filteredOptions[index])}
+ >
+ {capitalizeEachWord(filteredOptions[index].hierarchy)}
+
+ {/* Show "Remove" button when the category is selected */}
+ {selectedCategories.some(
+ (cat) => cat.hierarchy === filteredOptions[index].hierarchy
+ ) && (
+
+ )}
+
+ {/* Show "Add" button when the category is not included in the predefined or userdefined lists */}
+ {!flattenedCategories.some(
+ (cat) => cat.hierarchy === filteredOptions[index].hierarchy
+ ) &&
+ !flattenedUserCategories.some(
+ (cat) => cat.hierarchy === filteredOptions[index].hierarchy
+ ) && (
+
+ )}
+
+ )
+ }
+
+ return (
+
+
+
You can select multiple categories
+
+
+
+
+
+
+ {filteredOptions.length > 0 ? (
+ filteredOptions.map((c, i) =>
|
)
+ ) : (
+
+ {inputValue &&
+ !filteredOptions?.find(
+ (option) =>
+ option.hierarchy.toLowerCase() === inputValue.toLowerCase()
+ ) ? (
+ <>
+ Add "{inputValue}"
+
+ >
+ ) : (
+ <>No matches>
+ )}
+
+ )}
+
+
+
+ {LTags.length > 0 && (
+
+ {LTags.map((hierarchy) => {
+ const hierarchicalCategories = hierarchy.split(`:`)
+ const categories = hierarchicalCategories
+ .map
((c, i) => {
+ const partialHierarchy = hierarchicalCategories
+ .slice(0, i + 1)
+ .join(':')
+
+ return game ? (
+
+ {capitalizeEachWord(c)}
+
+ ) : (
+
+ {capitalizeEachWord(c)}
+
+ )
+ })
+ .reduce((prev, curr, i) => [
+ prev,
+ ,
+ curr
+ ])
+
+ return (
+
+ {categories}
+
+ )
+ })}
+
+ )}
+
+ )
+}
diff --git a/src/components/Filters/BlogsFilter.tsx b/src/components/Filters/BlogsFilter.tsx
index 71b4098..5efc7d2 100644
--- a/src/components/Filters/BlogsFilter.tsx
+++ b/src/components/Filters/BlogsFilter.tsx
@@ -1,16 +1,11 @@
import { useAppSelector, useLocalStorage } from 'hooks'
import React from 'react'
-import {
- FilterOptions,
- ModeratedFilter,
- NSFWFilter,
- SortBy,
- WOTFilterOptions
-} from 'types'
+import { FilterOptions, ModeratedFilter, SortBy, WOTFilterOptions } from 'types'
import { DEFAULT_FILTER_OPTIONS } from 'utils'
import { Dropdown } from './Dropdown'
import { Option } from './Option'
import { Filter } from '.'
+import { NsfwFilterOptions } from './NsfwFilterOptions'
type Props = {
author?: string | undefined
@@ -115,19 +110,7 @@ export const BlogsFilter = React.memo(
{/* nsfw filter options */}
- {Object.values(NSFWFilter).map((item, index) => (
-
- ))}
+
{/* source filter options */}
diff --git a/src/components/Filters/CategoryFilterPopup.module.scss b/src/components/Filters/CategoryFilterPopup.module.scss
new file mode 100644
index 0000000..752a21e
--- /dev/null
+++ b/src/components/Filters/CategoryFilterPopup.module.scss
@@ -0,0 +1,3 @@
+.noResult:not(:only-child) {
+ display: none;
+}
diff --git a/src/components/Filters/CategoryFilterPopup.tsx b/src/components/Filters/CategoryFilterPopup.tsx
new file mode 100644
index 0000000..d4abdfe
--- /dev/null
+++ b/src/components/Filters/CategoryFilterPopup.tsx
@@ -0,0 +1,506 @@
+import React, { useEffect, useMemo, useState } from 'react'
+import { createPortal } from 'react-dom'
+import { Category } from 'types'
+import {
+ addToUserCategories,
+ capitalizeEachWord,
+ deleteFromUserCategories,
+ flattenCategories
+} from 'utils'
+import { useLocalStorage } from 'hooks'
+import styles from './CategoryFilterPopup.module.scss'
+import categoriesData from './../../assets/categories/categories.json'
+
+interface CategoryFilterPopupProps {
+ categories: string[]
+ setCategories: React.Dispatch>
+ hierarchies: string[]
+ setHierarchies: React.Dispatch>
+ handleClose: () => void
+}
+
+export const CategoryFilterPopup = ({
+ categories,
+ setCategories,
+ hierarchies,
+ setHierarchies,
+ handleClose
+}: CategoryFilterPopupProps) => {
+ const [userHierarchies, setUserHierarchies] = useLocalStorage<
+ (string | Category)[]
+ >('user-hierarchies', [])
+ const [filterCategories, setFilterCategories] = useState(categories)
+ const [filterHierarchies, setFilterHierarchies] = useState(hierarchies)
+ const handleApply = () => {
+ setCategories(filterCategories)
+ setHierarchies(filterHierarchies)
+ }
+ const [inputValue, setInputValue] = useState('')
+ const userHierarchiesMatching = useMemo(
+ () =>
+ flattenCategories(userHierarchies, []).some((h) =>
+ h.hierarchy.includes(inputValue.toLowerCase())
+ ),
+ [inputValue, userHierarchies]
+ )
+ // const hierarchiesMatching = useMemo(
+ // () =>
+ // flattenCategories(categoriesData, []).some((h) =>
+ // h.hierarchy.includes(inputValue.toLowerCase())
+ // ),
+ // [inputValue]
+ // )
+
+ const handleInputChange = (e: React.ChangeEvent) => {
+ setInputValue(e.target.value)
+ }
+ const handleSingleSelection = (category: string, isSelected: boolean) => {
+ let updatedCategories = [...filterCategories]
+ if (isSelected) {
+ updatedCategories.push(category)
+ } else {
+ updatedCategories = updatedCategories.filter((item) => item !== category)
+ }
+ setFilterCategories(updatedCategories)
+ }
+ const handleCombinationSelection = (path: string[], isSelected: boolean) => {
+ const pathString = path.join(':')
+ let updatedHierarchies = [...filterHierarchies]
+ if (isSelected) {
+ updatedHierarchies.push(pathString)
+ } else {
+ updatedHierarchies = updatedHierarchies.filter(
+ (item) => item !== pathString
+ )
+ }
+ setFilterHierarchies(updatedHierarchies)
+ }
+ const handleAddNew = () => {
+ if (inputValue) {
+ const value = inputValue.toLowerCase()
+ const values = value
+ .trim()
+ .split('>')
+ .map((s) => s.trim())
+
+ setUserHierarchies((prev) => {
+ addToUserCategories(prev, value)
+ return [...prev]
+ })
+
+ const path = values.join(':')
+
+ // Add new hierarchy to current selection and active selection
+ // Convert through set to remove duplicates
+ setFilterHierarchies((prev) => {
+ prev.push(path)
+ return Array.from(new Set([...prev]))
+ })
+ setHierarchies((prev) => {
+ prev.push(path)
+ return Array.from(new Set([...prev]))
+ })
+ setInputValue('')
+ }
+ }
+
+ return createPortal(
+
+
+
+
+
+
+
Categories filter
+
+
+
+
+
+
+
+
+ This is description for an input and how to use search here
+
+
+
+ {userHierarchies.length > 0 && (
+ <>
+
+
+
Maybe
+
+
+ {!userHierarchiesMatching &&
No results.
}
+ {userHierarchies
+ .filter((c) => typeof c !== 'string')
+ .map((c, i) => (
+
{
+ setUserHierarchies((prev) => {
+ deleteFromUserCategories(prev, path.join('>'))
+ return [...prev]
+ })
+
+ // Remove the deleted hierarchies from current filter selection and active selection
+ setFilterHierarchies((prev) =>
+ prev.filter(
+ (h) => !h.startsWith(path.join(':'))
+ )
+ )
+ setHierarchies((prev) =>
+ prev.filter(
+ (h) => !h.startsWith(path.join(':'))
+ )
+ )
+ }}
+ />
+ ))}
+
+ >
+ )}
+
+
+
+
Maybe
+
+
+
+
No results.
+
+ {userHierarchiesMatching ? (
+
Already defined in your categories
+ ) : (
+
+ Add and search for "{inputValue}" category
+
+
+ )}
+
+ {(categoriesData as Category[]).map((category) => {
+ const name =
+ typeof category === 'string' ? category : category.name
+ return (
+
+ )
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
,
+ document.body
+ )
+}
+
+interface CategoryCheckboxProps {
+ inputValue: string
+ category: Category | string
+ path: string[]
+ handleSingleSelection: (category: string, isSelected: boolean) => void
+ handleCombinationSelection: (path: string[], isSelected: boolean) => void
+ selectedSingles: string[]
+ selectedCombinations: string[]
+ indentLevel?: number
+ handleRemove?: (path: string[]) => void
+}
+
+const CategoryCheckbox: React.FC = ({
+ inputValue,
+ category,
+ path,
+ handleSingleSelection,
+ handleCombinationSelection,
+ selectedSingles,
+ selectedCombinations,
+ indentLevel = 0,
+ handleRemove
+}) => {
+ const name = typeof category === 'string' ? category : category.name
+ const isMatching = path
+ .join(' > ')
+ .toLowerCase()
+ .includes(inputValue.toLowerCase())
+ const [isSingleChecked, setIsSingleChecked] = useState(false)
+ const [isCombinationChecked, setIsCombinationChecked] =
+ useState(false)
+ const [isIndeterminate, setIsIndeterminate] = useState(false)
+
+ useEffect(() => {
+ const pathString = path.join(':')
+ setIsSingleChecked(selectedSingles.includes(name))
+ setIsCombinationChecked(selectedCombinations.includes(pathString))
+ // Recursive function to gather all descendant paths
+ const collectChildPaths = (
+ category: string | Category,
+ basePath: string[]
+ ) => {
+ if (!category.sub || !Array.isArray(category.sub)) {
+ return []
+ }
+ let paths: string[] = []
+ for (const sub of category.sub) {
+ const subPath =
+ typeof sub === 'string'
+ ? [...basePath, sub].join(':')
+ : [...basePath, sub.name].join(':')
+ paths.push(subPath)
+ if (typeof sub === 'object') {
+ paths = paths.concat(collectChildPaths(sub, [...basePath, sub.name]))
+ }
+ }
+ return paths
+ }
+ const childPaths = collectChildPaths(category, path)
+ const anyChildCombinationSelected = childPaths.some((childPath) =>
+ selectedCombinations.includes(childPath)
+ )
+ setIsIndeterminate(
+ anyChildCombinationSelected && !selectedCombinations.includes(pathString)
+ )
+ }, [category, name, path, selectedCombinations, selectedSingles])
+
+ const handleSingleChange = () => {
+ setIsSingleChecked(!isSingleChecked)
+ handleSingleSelection(name, !isSingleChecked)
+ }
+
+ const handleCombinationChange = () => {
+ setIsCombinationChecked(!isCombinationChecked)
+ handleCombinationSelection(path, !isCombinationChecked)
+ }
+
+ return (
+ <>
+ {isMatching && (
+
+ )}
+ {typeof category !== 'string' &&
+ category.sub &&
+ Array.isArray(category.sub) && (
+ <>
+ {category.sub.map((subCategory) => {
+ if (typeof subCategory === 'string') {
+ return (
+
+ )
+ } else {
+ return (
+
+ )
+ }
+ })}
+ >
+ )}
+ >
+ )
+}
diff --git a/src/components/Filters/ModsFilter.tsx b/src/components/Filters/ModsFilter.tsx
index afa43c4..44f182d 100644
--- a/src/components/Filters/ModsFilter.tsx
+++ b/src/components/Filters/ModsFilter.tsx
@@ -1,17 +1,17 @@
import { useAppSelector, useLocalStorage } from 'hooks'
-import React from 'react'
+import React, { PropsWithChildren } from 'react'
import {
FilterOptions,
SortBy,
ModeratedFilter,
WOTFilterOptions,
- NSFWFilter,
RepostFilter
} from 'types'
import { DEFAULT_FILTER_OPTIONS } from 'utils'
import { Filter } from '.'
import { Dropdown } from './Dropdown'
import { Option } from './Option'
+import { NsfwFilterOptions } from './NsfwFilterOptions'
type Props = {
author?: string | undefined
@@ -19,7 +19,7 @@ type Props = {
}
export const ModFilter = React.memo(
- ({ author, filterKey = 'filter' }: Props) => {
+ ({ author, filterKey = 'filter', children }: PropsWithChildren) => {
const userState = useAppSelector((state) => state.user)
const [filterOptions, setFilterOptions] = useLocalStorage(
filterKey,
@@ -115,19 +115,7 @@ export const ModFilter = React.memo(
{/* nsfw filter options */}
- {Object.values(NSFWFilter).map((item, index) => (
-
- ))}
+
{/* repost filter options */}
@@ -176,6 +164,8 @@ export const ModFilter = React.memo(
Show All
+
+ {children}
)
}
diff --git a/src/components/Filters/NsfwFilterOptions.tsx b/src/components/Filters/NsfwFilterOptions.tsx
new file mode 100644
index 0000000..05ab906
--- /dev/null
+++ b/src/components/Filters/NsfwFilterOptions.tsx
@@ -0,0 +1,64 @@
+import { FilterOptions, NSFWFilter } from 'types'
+import { Option } from './Option'
+import { NsfwAlertPopup } from 'components/NsfwAlertPopup'
+import { useState } from 'react'
+import { useLocalStorage } from 'hooks'
+import { DEFAULT_FILTER_OPTIONS } from 'utils'
+
+interface NsfwFilterOptionsProps {
+ filterKey: string
+}
+
+export const NsfwFilterOptions = ({ filterKey }: NsfwFilterOptionsProps) => {
+ const [, setFilterOptions] = useLocalStorage(
+ filterKey,
+ DEFAULT_FILTER_OPTIONS
+ )
+ const [showNsfwPopup, setShowNsfwPopup] = useState(false)
+ const [selectedNsfwOption, setSelectedNsfwOption] = useState<
+ NSFWFilter | undefined
+ >()
+ const [confirmNsfw] = useLocalStorage('confirm-nsfw', false)
+ const handleConfirm = (confirm: boolean) => {
+ if (confirm && selectedNsfwOption) {
+ setFilterOptions((prev) => ({
+ ...prev,
+ nsfw: selectedNsfwOption
+ }))
+ }
+ }
+
+ return (
+ <>
+ {Object.values(NSFWFilter).map((item, index) => (
+
+ ))}
+ {showNsfwPopup && (
+ setShowNsfwPopup(false)}
+ />
+ )}
+ >
+ )
+}
diff --git a/src/components/ModForm.tsx b/src/components/ModForm.tsx
index ea54912..1db8b96 100644
--- a/src/components/ModForm.tsx
+++ b/src/components/ModForm.tsx
@@ -10,7 +10,7 @@ import React, {
} from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify'
-import { FixedSizeList as List } from 'react-window'
+import { FixedSizeList } from 'react-window'
import { v4 as uuidv4 } from 'uuid'
import { T_TAG_VALUE } from '../constants'
import { useAppSelector, useGames, useNDKContext } from '../hooks'
@@ -30,6 +30,8 @@ import { CheckboxField, InputError, InputField } from './Inputs'
import { LoadingSpinner } from './LoadingSpinner'
import { NDKEvent } from '@nostr-dev-kit/ndk'
import { OriginalAuthor } from './OriginalAuthor'
+import { CategoryAutocomplete } from './CategoryAutocomplete'
+import { AlertPopup } from './AlertPopup'
interface FormErrors {
game?: string
@@ -70,9 +72,10 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
useEffect(() => {
if (location.pathname === appRoutes.submitMod) {
+ // Only trigger when the pathname changes to submit-mod
setFormState(initializeFormState())
}
- }, [location.pathname]) // Only trigger when the pathname changes to submit-mod
+ }, [location.pathname])
useEffect(() => {
if (existingModData) {
@@ -174,6 +177,27 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
[]
)
+ const [showConfirmPopup, setShowConfirmPopup] = useState(false)
+ const handleReset = () => {
+ setShowConfirmPopup(true)
+ }
+ const handleResetConfirm = (confirm: boolean) => {
+ setShowConfirmPopup(false)
+
+ // Cancel if not confirmed
+ if (!confirm) return
+
+ // Editing
+ if (existingModData) {
+ // Reset fields to the original existing data
+ setFormState(initializeFormState(existingModData))
+ return
+ }
+
+ // New - set form state to the initial (clear form state)
+ setFormState(initializeFormState())
+ }
+
const handlePublish = async () => {
setIsPublishing(true)
@@ -231,6 +255,21 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
tags.push(['originalAuthor', formState.originalAuthor])
}
+ // Prepend com.degmods to avoid leaking categories to 3rd party client's search
+ // Add hierarchical namespaces labels
+ if (formState.LTags.length > 0) {
+ for (let i = 0; i < formState.LTags.length; i++) {
+ tags.push(['L', `com.degmods:${formState.LTags[i]}`])
+ }
+ }
+
+ // Add category labels
+ if (formState.lTags.length > 0) {
+ for (let i = 0; i < formState.lTags.length; i++) {
+ tags.push(['l', `com.degmods:${formState.lTags[i]}`])
+ }
+ }
+
const unsignedEvent: UnsignedEvent = {
kind: kinds.ClassifiedListing,
created_at: currentTimeStamp,
@@ -445,6 +484,7 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
className='btn btnMain btnMainAdd'
type='button'
onClick={addScreenshotUrl}
+ title='Add'
>