feat: completed basic implementation for mod submission

This commit is contained in:
daniyal 2024-07-24 15:13:28 +05:00
parent deaf0ddac9
commit 2a503d6e4a
14 changed files with 570 additions and 525 deletions

1
.env.example Normal file
View File

@ -0,0 +1 @@
VITE_APP_RELAY=wss://relay.degmods.com

2
.gitignore vendored
View File

@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.env

458
package-lock.json generated
View File

@ -19,9 +19,7 @@
"react-quill": "2.0.0",
"react-redux": "9.1.2",
"react-router-dom": "^6.24.1",
"react-select": "5.8.0",
"react-toastify": "10.0.5",
"react-virtualized-auto-sizer": "1.0.24",
"react-window": "1.8.10",
"uuid": "10.0.0"
},
@ -30,7 +28,6 @@
"@types/papaparse": "5.3.14",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-select": "5.0.1",
"@types/react-window": "1.8.8",
"@types/uuid": "10.0.0",
"@typescript-eslint/eslint-plugin": "^7.13.1",
@ -61,6 +58,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.24.7",
"picocolors": "^1.0.0"
@ -121,6 +119,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz",
"integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.24.7",
"@jridgewell/gen-mapping": "^0.3.5",
@ -160,6 +159,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
"integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
"dev": true,
"dependencies": {
"@babel/types": "^7.24.7"
},
@ -171,6 +171,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
"integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
"dev": true,
"dependencies": {
"@babel/template": "^7.24.7",
"@babel/types": "^7.24.7"
@ -183,6 +184,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
"integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
"dev": true,
"dependencies": {
"@babel/types": "^7.24.7"
},
@ -194,6 +196,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
"integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
"dev": true,
"dependencies": {
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7"
@ -247,6 +250,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
"integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.24.7"
},
@ -258,6 +262,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz",
"integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
@ -266,6 +271,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
@ -296,6 +302,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.24.7",
"chalk": "^2.4.2",
@ -310,6 +317,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz",
"integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
@ -362,6 +370,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
"integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/parser": "^7.24.7",
@ -375,6 +384,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz",
"integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.24.7",
@ -395,6 +405,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz",
"integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.24.7",
"@babel/helper-validator-identifier": "^7.24.7",
@ -426,125 +437,6 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@emotion/babel-plugin": {
"version": "11.11.0",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
"integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==",
"dependencies": {
"@babel/helper-module-imports": "^7.16.7",
"@babel/runtime": "^7.18.3",
"@emotion/hash": "^0.9.1",
"@emotion/memoize": "^0.8.1",
"@emotion/serialize": "^1.1.2",
"babel-plugin-macros": "^3.1.0",
"convert-source-map": "^1.5.0",
"escape-string-regexp": "^4.0.0",
"find-root": "^1.1.0",
"source-map": "^0.5.7",
"stylis": "4.2.0"
}
},
"node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
},
"node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@emotion/cache": {
"version": "11.11.0",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz",
"integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==",
"dependencies": {
"@emotion/memoize": "^0.8.1",
"@emotion/sheet": "^1.2.2",
"@emotion/utils": "^1.2.1",
"@emotion/weak-memoize": "^0.3.1",
"stylis": "4.2.0"
}
},
"node_modules/@emotion/hash": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz",
"integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ=="
},
"node_modules/@emotion/memoize": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
},
"node_modules/@emotion/react": {
"version": "11.11.4",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz",
"integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==",
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.11.0",
"@emotion/cache": "^11.11.0",
"@emotion/serialize": "^1.1.3",
"@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
"@emotion/utils": "^1.2.1",
"@emotion/weak-memoize": "^0.3.1",
"hoist-non-react-statics": "^3.3.1"
},
"peerDependencies": {
"react": ">=16.8.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@emotion/serialize": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz",
"integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==",
"dependencies": {
"@emotion/hash": "^0.9.1",
"@emotion/memoize": "^0.8.1",
"@emotion/unitless": "^0.8.1",
"@emotion/utils": "^1.2.1",
"csstype": "^3.0.2"
}
},
"node_modules/@emotion/sheet": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz",
"integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA=="
},
"node_modules/@emotion/unitless": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
},
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz",
"integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==",
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@emotion/utils": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz",
"integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg=="
},
"node_modules/@emotion/weak-memoize": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
"integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@ -1006,28 +898,6 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@floating-ui/core": {
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz",
"integrity": "sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA==",
"dependencies": {
"@floating-ui/utils": "^0.2.4"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.7.tgz",
"integrity": "sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==",
"dependencies": {
"@floating-ui/core": "^1.6.0",
"@floating-ui/utils": "^0.2.4"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.4.tgz",
"integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA=="
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@ -1089,6 +959,7 @@
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
@ -1102,6 +973,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
@ -1110,6 +982,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
@ -1117,12 +990,14 @@
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
@ -1693,15 +1568,11 @@
"@types/node": "*"
}
},
"node_modules/@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="
},
"node_modules/@types/prop-types": {
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
"devOptional": true
},
"node_modules/@types/quill": {
"version": "1.3.10",
@ -1715,6 +1586,7 @@
"version": "18.3.3",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
"integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
"devOptional": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@ -1729,24 +1601,6 @@
"@types/react": "*"
}
},
"node_modules/@types/react-select": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@types/react-select/-/react-select-5.0.1.tgz",
"integrity": "sha512-h5Im0AP0dr4AVeHtrcvQrLV+gmPa7SA0AGdxl2jOhtwiE6KgXBFSogWw8az32/nusE6AQHlCOHQWjP1S/+oMWA==",
"deprecated": "This is a stub types definition. react-select provides its own type definitions, so you do not need this installed.",
"dev": true,
"dependencies": {
"react-select": "*"
}
},
"node_modules/@types/react-transition-group": {
"version": "4.4.10",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
"integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-window": {
"version": "1.8.8",
"resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz",
@ -2039,6 +1893,7 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
@ -2080,20 +1935,6 @@
"node": ">=8"
}
},
"node_modules/babel-plugin-macros": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
"integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"cosmiconfig": "^7.0.0",
"resolve": "^1.19.0"
},
"engines": {
"node": ">=10",
"npm": ">=6"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -2199,6 +2040,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"engines": {
"node": ">=6"
}
@ -2236,6 +2078,7 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@ -2301,6 +2144,7 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
@ -2308,7 +2152,8 @@
"node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/concat-map": {
"version": "0.0.1",
@ -2322,21 +2167,6 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true
},
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
"dependencies": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.2.1",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.10.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@ -2360,7 +2190,8 @@
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"devOptional": true
},
"node_modules/d": {
"version": "1.0.2",
@ -2488,29 +2319,12 @@
"node": ">=6.0.0"
}
},
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.823",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.823.tgz",
"integrity": "sha512-4h+oPeAiGQOHFyUJOqpoEcPj/xxlicxBzOErVeYVMMmAiXUXsGpsFd0QXBMaUUbnD8hhSfLf9uw+MlsoIA7j5w==",
"dev": true
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dependencies": {
"is-arrayish": "^0.2.1"
}
},
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
@ -2618,6 +2432,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
@ -3051,11 +2866,6 @@
"node": ">=8"
}
},
"node_modules/find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
},
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@ -3225,6 +3035,7 @@
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true,
"engines": {
"node": ">=4"
}
@ -3270,6 +3081,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
}
@ -3332,14 +3144,6 @@
"node": ">= 0.4"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/ignore": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
@ -3368,6 +3172,7 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
@ -3420,11 +3225,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -3437,20 +3237,6 @@
"node": ">=8"
}
},
"node_modules/is-core-module": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz",
"integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==",
"dependencies": {
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-date-object": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
@ -3551,6 +3337,7 @@
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true,
"bin": {
"jsesc": "bin/jsesc"
},
@ -3564,11 +3351,6 @@
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@ -3634,11 +3416,6 @@
}
]
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -3973,14 +3750,6 @@
"integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==",
"optional": true
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
@ -4074,6 +3843,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"dependencies": {
"callsites": "^3.0.0"
},
@ -4081,23 +3851,6 @@
"node": ">=6"
}
},
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"dependencies": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
"json-parse-even-better-errors": "^2.3.0",
"lines-and-columns": "^1.1.6"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -4125,15 +3878,11 @@
"node": ">=8"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -4141,7 +3890,8 @@
"node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
"dev": true
},
"node_modules/picomatch": {
"version": "2.3.1",
@ -4211,16 +3961,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -4299,11 +4039,6 @@
"react": "^18.3.1"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-quill": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-quill/-/react-quill-2.0.0.tgz",
@ -4379,31 +4114,6 @@
"react-dom": ">=16.8"
}
},
"node_modules/react-select": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.0.tgz",
"integrity": "sha512-TfjLDo58XrhP6VG5M/Mi56Us0Yt8X7xD6cDybC7yoRMUNm7BGO7qk8J0TLQOua/prb8vUOtsfnXZwfm30HGsAA==",
"dependencies": {
"@babel/runtime": "^7.12.0",
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.8.1",
"@floating-ui/dom": "^1.0.1",
"@types/react-transition-group": "^4.4.0",
"memoize-one": "^6.0.0",
"prop-types": "^15.6.0",
"react-transition-group": "^4.3.0",
"use-isomorphic-layout-effect": "^1.1.2"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-select/node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
},
"node_modules/react-toastify": {
"version": "10.0.5",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz",
@ -4416,30 +4126,6 @@
"react-dom": ">=18"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/react-virtualized-auto-sizer": {
"version": "1.0.24",
"resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.24.tgz",
"integrity": "sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg==",
"peerDependencies": {
"react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0",
"react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-window": {
"version": "1.8.10",
"resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz",
@ -4508,26 +4194,11 @@
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="
},
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dependencies": {
"is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"engines": {
"node": ">=4"
}
@ -4713,14 +4384,6 @@
"node": ">=8"
}
},
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
@ -4754,15 +4417,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
},
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
@ -4770,17 +4429,6 @@
"node": ">=4"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -4791,6 +4439,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
"dev": true,
"engines": {
"node": ">=4"
}
@ -4983,19 +4632,6 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-isomorphic-layout-effect": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
"integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-sync-external-store": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
@ -5187,14 +4823,6 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
},
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"engines": {
"node": ">= 6"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",

View File

@ -21,9 +21,7 @@
"react-quill": "2.0.0",
"react-redux": "9.1.2",
"react-router-dom": "^6.24.1",
"react-select": "5.8.0",
"react-toastify": "10.0.5",
"react-virtualized-auto-sizer": "1.0.24",
"react-window": "1.8.10",
"uuid": "10.0.0"
},
@ -32,7 +30,6 @@
"@types/papaparse": "5.3.14",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-select": "5.0.1",
"@types/react-window": "1.8.8",
"@types/uuid": "10.0.0",
"@typescript-eslint/eslint-plugin": "^7.13.1",

View File

@ -1,7 +1,8 @@
import '../styles/styles.css'
import ReactQuill from 'react-quill'
import '../styles/customQuillStyles.css'
import React from 'react'
import ReactQuill from 'react-quill'
import 'react-quill/dist/quill.snow.css'
import '../styles/customQuillStyles.css'
import '../styles/styles.css'
const editorFormats = [
'header',
@ -41,6 +42,7 @@ interface InputFieldProps {
name: string
inputMode?: 'url'
value: string
error?: string
onChange: (name: string, value: string) => void
}
@ -53,6 +55,7 @@ export const InputField = React.memo(
name,
inputMode,
value,
error,
onChange
}: InputFieldProps) => {
const handleChange = (
@ -93,11 +96,27 @@ export const InputField = React.memo(
onChange={handleChange}
/>
)}
{error && <InputError message={error} />}
</div>
)
}
)
type InputErrorProps = {
message: string
}
export const InputError = ({ message }: InputErrorProps) => {
if (!message) return null
return (
<div className='errorMain'>
<div className='errorMainColor'></div>
<p className='errorMainText'>{message}</p>
</div>
)
}
interface CheckboxFieldProps {
label: string
name: string

View File

@ -1,16 +1,22 @@
import _ from 'lodash'
import { Event, kinds, UnsignedEvent } from 'nostr-tools'
import Papa from 'papaparse'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import 'react-quill/dist/quill.snow.css'
import { FixedSizeList as List } from 'react-window'
import { kinds, UnsignedEvent } from 'nostr-tools'
import { toast } from 'react-toastify'
import { FixedSizeList as List } from 'react-window'
import { v4 as uuidv4 } from 'uuid'
import { useAppSelector } from '../hooks'
import '../styles/styles.css'
import { now } from '../utils'
import { CheckboxField, InputField } from './Inputs'
import {
isReachable,
isValidImageUrl,
isValidUrl,
log,
LogType,
now
} from '../utils'
import { CheckboxField, InputError, InputField } from './Inputs'
import { RelayController } from '../controllers'
interface DownloadUrl {
url: string
@ -33,6 +39,18 @@ interface FormState {
downloadUrls: DownloadUrl[]
}
interface FormErrors {
game?: string
title?: string
body?: string
featuredImageUrl?: string
summary?: string
nsfw?: string
screenshotsUrls?: string[]
tags?: string
downloadUrls?: string[]
}
interface GameOption {
value: string
label: string
@ -43,8 +61,8 @@ let processedCSV = false
export const ModForm = () => {
const userState = useAppSelector((state) => state.user)
const [isPublishing, setIsPublishing] = useState(false)
const [gameOptions, setGameOptions] = useState<GameOption[]>([])
const [formState, setFormState] = useState<FormState>({
game: '',
title: '',
@ -65,6 +83,7 @@ export const ModForm = () => {
}
]
})
const [formErrors, setFormErrors] = useState<FormErrors>({})
useEffect(() => {
if (processedCSV) return
@ -181,6 +200,8 @@ export const ModForm = () => {
)
const handlePublish = async () => {
setIsPublishing(true)
let hexPubkey: string
if (userState.isAuth && userState.user?.pubkey) {
@ -194,25 +215,30 @@ export const ModForm = () => {
return
}
if (!validateState()) return
if (!(await validateState())) {
setIsPublishing(false)
return
}
const uuid = uuidv4()
const currentTimeStamp = now()
const unsignedEvent: UnsignedEvent = {
kind: kinds.ClassifiedListing,
created_at: now(),
created_at: currentTimeStamp,
pubkey: hexPubkey,
content: formState.body,
tags: [
['d', uuid],
// todo: t tag for degmod identifier (use url)
['t', window.location.host],
['published_at', currentTimeStamp.toString()],
['game', formState.game],
['title', formState.title],
['featuredImageUrl', formState.featuredImageUrl],
['summary', formState.summary],
['nsfw', formState.nsfw.toString()],
['screenshotsUrls', ...formState.screenshotsUrls],
['tags', formState.tags],
['tags', ...formState.tags.split(',')],
[
'downloadUrls',
...formState.downloadUrls.map((downloadUrl) =>
@ -222,60 +248,117 @@ export const ModForm = () => {
]
}
const signedEvent = await window.nostr?.signEvent(unsignedEvent)
console.log('signedEvent :>> ', signedEvent)
const signedEvent = await window.nostr
?.signEvent(unsignedEvent)
.catch((err) => {
toast.error('Failed to sign the event!')
log(true, LogType.Error, 'Failed to sign the event!', err)
return null
})
// todo: publish event
if (!signedEvent) {
setIsPublishing(false)
return
}
const publishedOnRelays = await RelayController.getInstance().publish(
signedEvent as Event
)
console.log('publishedOnRelays :>> ', publishedOnRelays)
if (!publishedOnRelays) {
toast.error('Failed to publish event!')
setIsPublishing(false)
return
}
// Handle cases where publishing failed or succeeded
if (publishedOnRelays.length === 0) {
toast.error('Failed to publish event on any relay')
} else {
toast.success(
`Event published successfully to the following relays\n\n${publishedOnRelays.join(
'\n'
)}`
)
}
setIsPublishing(false)
}
const validateState = () => {
const validateState = async (): Promise<boolean> => {
const errors: FormErrors = {}
if (formState.game === '') {
toast.error('Game field can not be empty')
return false
errors.game = 'Game field can not be empty'
}
if (formState.title === '') {
toast.error('Title field can not be empty')
return false
errors.title = 'Title field can not be empty'
}
if (formState.body === '') {
toast.error('Body field can not be empty')
return false
errors.body = 'Body field can not be empty'
}
if (formState.featuredImageUrl === '') {
toast.error('FeaturedImageUrl field can not be empty')
return false
errors.featuredImageUrl = 'FeaturedImageUrl field can not be empty'
} else if (
!isValidImageUrl(formState.featuredImageUrl) ||
!(await isReachable(formState.featuredImageUrl))
) {
errors.featuredImageUrl =
'FeaturedImageUrl must be a valid and reachable image URL'
}
if (formState.summary === '') {
toast.error('Summary field can not be empty')
return false
errors.summary = 'Summary field can not be empty'
}
if (
formState.screenshotsUrls.length === 0 ||
formState.screenshotsUrls.every((url) => url === '')
) {
toast.error('Required at least one screenshot url')
return false
if (formState.screenshotsUrls.length === 0) {
errors.screenshotsUrls = ['Required at least one screenshot url']
} else {
for (let i = 0; i < formState.screenshotsUrls.length; i++) {
const url = formState.screenshotsUrls[i]
if (
!isValidUrl(url) ||
!isValidImageUrl(url) ||
!(await isReachable(url))
) {
if (!errors.screenshotsUrls)
errors.screenshotsUrls = Array(formState.screenshotsUrls.length)
errors.screenshotsUrls![i] =
'All screenshot URLs must be valid and reachable image URLs'
}
}
}
if (formState.tags === '') {
toast.error('tags field can not be empty')
return false
errors.tags = 'Tags field can not be empty'
}
if (
formState.downloadUrls.length === 0 ||
formState.downloadUrls.every((download) => download.url === '')
) {
toast.error('Required at least one download url')
return false
if (formState.downloadUrls.length === 0) {
errors.downloadUrls = ['Required at least one download url']
} else {
for (let i = 0; i < formState.downloadUrls.length; i++) {
const downloadUrl = formState.downloadUrls[i]
if (
!isValidUrl(downloadUrl.url) ||
!(await isReachable(downloadUrl.url))
) {
if (!errors.downloadUrls)
errors.downloadUrls = Array(formState.downloadUrls.length)
errors.downloadUrls![i] = 'Download url must be valid and reachable'
}
}
}
return true
setFormErrors(errors)
return Object.keys(errors).length === 0
}
return (
@ -283,6 +366,7 @@ export const ModForm = () => {
<GameDropdown
options={gameOptions}
selected={formState.game}
error={formErrors.game}
onChange={handleInputChange}
/>
@ -291,6 +375,7 @@ export const ModForm = () => {
placeholder='Return the banana mod'
name='title'
value={formState.title}
error={formErrors.title}
onChange={handleInputChange}
/>
@ -300,6 +385,7 @@ export const ModForm = () => {
placeholder="Here's what this mod is all about"
name='body'
value={formState.body}
error={formErrors.body}
onChange={handleInputChange}
/>
@ -311,6 +397,7 @@ export const ModForm = () => {
placeholder='Image URL'
name='featuredImageUrl'
value={formState.featuredImageUrl}
error={formErrors.featuredImageUrl}
onChange={handleInputChange}
/>
<InputField
@ -319,6 +406,7 @@ export const ModForm = () => {
placeholder='This is a quick description of my mod'
name='summary'
value={formState.summary}
error={formErrors.summary}
onChange={handleInputChange}
/>
<CheckboxField
@ -350,14 +438,25 @@ export const ModForm = () => {
We recommend to upload images to https://nostr.build/
</p>
{formState.screenshotsUrls.map((url, index) => (
<ScreenshotUrlFields
key={index}
index={index}
url={url}
onUrlChange={handleScreenshotUrlChange}
onRemove={removeScreenshotUrl}
/>
<>
<ScreenshotUrlFields
key={index}
index={index}
url={url}
onUrlChange={handleScreenshotUrlChange}
onRemove={removeScreenshotUrl}
/>
{formErrors.screenshotsUrls &&
formErrors.screenshotsUrls[index] && (
<InputError message={formErrors.screenshotsUrls[index]} />
)}
</>
))}
{formState.screenshotsUrls.length === 0 &&
formErrors.screenshotsUrls &&
formErrors.screenshotsUrls[0] && (
<InputError message={formErrors.screenshotsUrls[0]} />
)}
</div>
<InputField
label='Tags'
@ -365,6 +464,7 @@ export const ModForm = () => {
placeholder='Tags'
name='tags'
value={formState.tags}
error={formErrors.tags}
onChange={handleInputChange}
/>
<div className='inputLabelWrapperMain'>
@ -393,17 +493,38 @@ export const ModForm = () => {
</p>
{formState.downloadUrls.map((download, index) => (
<DownloadUrlFields
key={index}
index={index}
download={download}
onUrlChange={handleDownloadUrlChange}
onRemove={removeDownloadUrl}
/>
<>
<DownloadUrlFields
key={index}
index={index}
url={download.url}
hash={download.hash}
signatureKey={download.signatureKey}
malwareScanLink={download.malwareScanLink}
modVersion={download.modVersion}
customNote={download.customNote}
onUrlChange={handleDownloadUrlChange}
onRemove={removeDownloadUrl}
/>
{formErrors.downloadUrls && formErrors.downloadUrls[index] && (
<InputError message={formErrors.downloadUrls[index]} />
)}
</>
))}
{formState.downloadUrls.length === 0 &&
formErrors.downloadUrls &&
formErrors.downloadUrls[0] && (
<InputError message={formErrors.downloadUrls[0]} />
)}
</div>
<div className='IBMSMSMBS_WriteAction'>
<button className='btn btnMain' type='button' onClick={handlePublish}>
<button
className='btn btnMain'
type='button'
onClick={handlePublish}
disabled={isPublishing}
>
Publish
</button>
</div>
@ -412,13 +533,28 @@ export const ModForm = () => {
}
type DownloadUrlFieldsProps = {
index: number
download: DownloadUrl
url: string
hash: string
signatureKey: string
malwareScanLink: string
modVersion: string
customNote: string
onUrlChange: (index: number, field: keyof DownloadUrl, value: string) => void
onRemove: (index: number) => void
}
const DownloadUrlFields = React.memo(
({ index, download, onUrlChange, onRemove }: DownloadUrlFieldsProps) => {
({
index,
url,
hash,
signatureKey,
malwareScanLink,
modVersion,
customNote,
onUrlChange,
onRemove
}: DownloadUrlFieldsProps) => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target
onUrlChange(index, name as keyof DownloadUrl, value)
@ -432,7 +568,7 @@ const DownloadUrlFields = React.memo(
className='inputMain'
name='url'
placeholder='Download URL'
value={download.url}
value={url}
onChange={handleChange}
/>
@ -469,7 +605,7 @@ const DownloadUrlFields = React.memo(
className='inputMain'
name='hash'
placeholder='SHA-256 Hash'
value={download.hash}
value={hash}
onChange={handleChange}
/>
<div className='inputWrapperMainBox'></div>
@ -491,7 +627,7 @@ const DownloadUrlFields = React.memo(
className='inputMain'
placeholder='Signature public key'
name='signatureKey'
value={download.signatureKey}
value={signatureKey}
onChange={handleChange}
/>
<div className='inputWrapperMainBox'></div>
@ -513,7 +649,7 @@ const DownloadUrlFields = React.memo(
className='inputMain'
name='malwareScanLink'
placeholder='Malware Scan Link'
value={download.malwareScanLink}
value={malwareScanLink}
onChange={handleChange}
/>
<div className='inputWrapperMainBox'></div>
@ -535,7 +671,7 @@ const DownloadUrlFields = React.memo(
className='inputMain'
placeholder='Mod version (1.0)'
name='modVersion'
value={download.modVersion}
value={modVersion}
onChange={handleChange}
/>
<div className='inputWrapperMainBox'></div>
@ -557,7 +693,7 @@ const DownloadUrlFields = React.memo(
className='inputMain'
placeholder='Custom note/message'
name='customNote'
value={download.customNote}
value={customNote}
onChange={handleChange}
/>
<div className='inputWrapperMainBox'></div>
@ -613,10 +749,16 @@ const ScreenshotUrlFields = React.memo(
type GameDropdownProps = {
options: GameOption[]
selected: string
error?: string
onChange: (name: string, value: string) => void
}
const GameDropdown = ({ options, selected, onChange }: GameDropdownProps) => {
const GameDropdown = ({
options,
selected,
error,
onChange
}: GameDropdownProps) => {
const [searchTerm, setSearchTerm] = useState('')
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('')
const inputRef = useRef<HTMLInputElement>(null)
@ -646,48 +788,66 @@ const GameDropdown = ({ options, selected, onChange }: GameDropdownProps) => {
can add it.
</p>
<div className='dropdown dropdownMain'>
<input
ref={inputRef}
type='text'
className='inputMain dropdown-toggle'
placeholder='This mod is for a game called...'
aria-expanded='false'
data-bs-toggle='dropdown'
value={searchTerm || selected}
onChange={(e) => handleSearchChange(e.target.value)}
onBlur={() => {
if (!isOptionClicked.current) {
setSearchTerm('')
}
isOptionClicked.current = false
}}
/>
<div className='dropdown-menu dropdownMainMenu'>
<List
height={500}
width={'100%'}
itemCount={filteredOptions.length}
itemSize={35}
<div className='inputWrapperMain'>
<input
ref={inputRef}
type='text'
className='inputMain inputMainWithBtn dropdown-toggle'
placeholder='This mod is for a game called...'
aria-expanded='false'
data-bs-toggle='dropdown'
value={searchTerm || selected}
onChange={(e) => handleSearchChange(e.target.value)}
onBlur={() => {
if (!isOptionClicked.current) {
setSearchTerm('')
}
isOptionClicked.current = false
}}
/>
<button
className='btn btnMain btnMainInsideField btnMainRemove'
type='button'
onClick={() => onChange('game', '')}
>
{({ index, style }) => (
<div
style={style}
className='dropdown-item dropdownMainMenuItem'
onMouseDown={() => (isOptionClicked.current = true)}
onClick={() => {
onChange('game', filteredOptions[index].value)
setSearchTerm('')
if (inputRef.current) {
inputRef.current.blur()
}
}}
>
{filteredOptions[index].label}
</div>
)}
</List>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 512 512'
width='1em'
height='1em'
fill='currentColor'
>
<path d='M480 416C497.7 416 512 430.3 512 448C512 465.7 497.7 480 480 480H150.6C133.7 480 117.4 473.3 105.4 461.3L25.37 381.3C.3786 356.3 .3786 315.7 25.37 290.7L258.7 57.37C283.7 32.38 324.3 32.38 349.3 57.37L486.6 194.7C511.6 219.7 511.6 260.3 486.6 285.3L355.9 416H480zM265.4 416L332.7 348.7L195.3 211.3L70.63 336L150.6 416L265.4 416z'></path>
</svg>
</button>
<div className='dropdown-menu dropdownMainMenu'>
<List
height={500}
width={'100%'}
itemCount={filteredOptions.length}
itemSize={35}
>
{({ index, style }) => (
<div
style={style}
className='dropdown-item dropdownMainMenuItem'
onMouseDown={() => (isOptionClicked.current = true)}
onClick={() => {
onChange('game', filteredOptions[index].value)
setSearchTerm('')
if (inputRef.current) {
inputRef.current.blur()
}
}}
>
{filteredOptions[index].label}
</div>
)}
</List>
</div>
</div>
</div>
{error && <InputError message={error} />}
</div>
)
}

View File

@ -1 +1,2 @@
export * from './metadata'
export * from './relay'

View File

@ -1,4 +1,4 @@
import NDK, { NDKUser } from '@nostr-dev-kit/ndk'
import NDK, { NDKRelayList, NDKUser } from '@nostr-dev-kit/ndk'
import { UserProfile } from '../types/user'
import { hexToNpub } from '../utils'
@ -41,4 +41,14 @@ export class MetadataController {
return await user.fetchProfile()
}
public findWriteRelays = async (hexKey: string) => {
const ndkRelayList = await NDKRelayList.forUser(hexKey, this.profileNdk)
if (!ndkRelayList) {
throw new Error(`Couldn't found user's relay list`)
}
return ndkRelayList.writeRelayUrls
}
}

121
src/controllers/relay.ts Normal file
View File

@ -0,0 +1,121 @@
import { Relay, Event } from 'nostr-tools'
import { log, LogType, timeout } from '../utils'
import { MetadataController } from './metadata'
import _ from 'lodash'
/**
* Singleton class to manage relay operations.
*/
export class RelayController {
private static instance: RelayController
private debug = true
public connectedRelays: Relay[] = []
private constructor() {}
private connectRelay = async (relayUrl: string) => {
const relay = this.connectedRelays.find(
(relay) => _.trimEnd(relay.url, '/') === _.trimEnd(relayUrl, '/')
)
if (relay) {
// already connected, skip
return relay
}
return await Relay.connect(relayUrl)
.then((relay) => {
log(this.debug, LogType.Info, `✅ nostr (${relayUrl}): Connected!`)
this.connectedRelays.push(relay)
return relay
})
.catch((err) => {
log(
this.debug,
LogType.Error,
`❌ nostr (${relayUrl}): Connection error!`,
err
)
return null
})
}
/**
* Provides the singleton instance of RelayController.
*
* @returns The singleton instance of RelayController.
*/
public static getInstance(): RelayController {
if (!RelayController.instance) {
RelayController.instance = new RelayController()
}
return RelayController.instance
}
publish = async (event: Event) => {
const appRelayPromise = this.connectRelay(import.meta.env.VITE_APP_RELAY)
// todo: window.nostr.getRelays() is not implemented yet in nostr-login, implement the logic once its done
const writeRelaysPromise = MetadataController.getInstance().findWriteRelays(
event.pubkey
)
log(this.debug, LogType.Info, `Finding user's write relays`)
const writeRelayUrls = await Promise.race([
writeRelaysPromise,
timeout()
]).catch((err) => {
log(this.debug, LogType.Error, err)
return [] as string[]
})
const relayPromises = writeRelayUrls.map((relayUrl) =>
this.connectRelay(relayUrl)
)
await Promise.allSettled([appRelayPromise, ...relayPromises])
if (this.connectedRelays.length === 0) {
log(this.debug, LogType.Error, 'No relay is connected!')
return null
}
const publishedOnRelays: string[] = []
const publishPromises = this.connectedRelays.map((relay) => {
log(
this.debug,
LogType.Info,
`⬆️ nostr (${relay.url}): Sending event:`,
event
)
return Promise.race([relay.publish(event), timeout(30000)])
.then((res) => {
log(
this.debug,
LogType.Info,
`⬆️ nostr (${relay.url}): Publish result:`,
res
)
publishedOnRelays.push(relay.url)
})
.catch((err) => {
log(
this.debug,
LogType.Error,
`❌ nostr (${relay.url}): Publish error!`,
err
)
})
})
await Promise.allSettled(publishPromises)
console.log('publishedOnRelays :>> ', publishedOnRelays)
return publishedOnRelays
}
}

View File

@ -291,6 +291,7 @@ h6 {
display: flex;
flex-direction: row;
grid-gap: 10px;
position: relative;
}
.inputWrapperMainWrapper {
@ -513,3 +514,39 @@ a:hover {
flex-direction: row;
grid-gap: 10px;
}
.btnMain.btnMainInsideField:hover {
background: unset;
}
.btnMain.btnMainInsideField {
position: absolute;
right: 0;
top: 0;
bottom: 0;
border-radius: unset;
}
.inputMain.inputMainWithBtn {
padding-right: 50px;
}
.errorMain {
width: 100%;
border-radius: 10px;
padding: 10px;
background: rgba(255, 255, 255, 0.05);
color: rgba(255, 255, 255, 0.5);
display: flex;
flex-direction: row;
grid-gap: 10px;
}
.errorMainColor {
width: 5px;
border-radius: 2px;
background: tomato;
}
.errorMainText {
}

View File

@ -1 +1,3 @@
export * from './nostr'
export * from './url'
export * from './utils'

22
src/utils/url.ts Normal file
View File

@ -0,0 +1,22 @@
export const isValidUrl = (url: string) => {
try {
new URL(url)
return true
} catch (_) {
return false
}
}
export const isValidImageUrl = (url: string) => {
const regex = /\.(jpeg|jpg|gif|png)$/
return regex.test(url)
}
export const isReachable = async (url: string) => {
try {
const response = await fetch(url, { method: 'HEAD' })
return response.ok
} catch (error) {
return false
}
}

36
src/utils/utils.ts Normal file
View File

@ -0,0 +1,36 @@
export enum LogType {
Info = 'info',
Error = 'error',
Warn = 'warn'
}
/**
* Log function to conditionally log messages to the console
*
* @param isOn boolean or undefined indicating if logging is enabled
* @param type LogType indicating the type of log (info, error, warn)
* @param args unknown[] represents the rest parameters for log messages
*/
export const log = (
isOn: boolean | undefined, // Flag to determine if logging is enabled
type: LogType, // Type of log (info, error, warn)
...args: unknown[] // Log messages to be printed
) => {
if (!isOn) return // If logging is not enabled, return early
console[type](...args) // Log the messages to the console with the specified log type
}
/**
* Creates a promise that rejects with a timeout error after a specified duration.
* @param ms The duration in milliseconds after which the promise should reject. Defaults to 60000 milliseconds (1 minute).
* @returns A promise that rejects with an Error('Timeout') after the specified duration.
*/
export const timeout = (ms: number = 60000) => {
return new Promise<never>((_, reject) => {
// Set a timeout using setTimeout
setTimeout(() => {
// Reject the promise with an Error indicating a timeout
reject(new Error('Timeout'))
}, ms) // Timeout duration in milliseconds
})
}

9
src/vite-env.d.ts vendored
View File

@ -1 +1,10 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_RELAY: string
// more env variables...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}