33 Commits

Author SHA1 Message Date
Nikolai Petukhov
964ca236e8 0.5.1 2024-10-12 12:33:46 +03:00
Nikolai Petukhov
9c1c670ccb small fix with routes 2024-10-12 12:25:38 +03:00
Nikolai Petukhov
ff9bd3ac8c 0.5.0 2024-10-12 12:24:16 +03:00
Nikolai Petukhov
51618c8858 small changes 2024-10-12 12:23:25 +03:00
Nikolai Petukhov
54f6c5c053 config fix 2024-10-12 11:39:17 +03:00
Nikolai Petukhov
8fecf7cb1f I have added intervals for messages 2024-10-11 13:39:15 +03:00
Nikolai Petukhov
49a8af611f done 2024-10-10 12:03:37 +03:00
Nikolai Petukhov
7c4457dea4 done 2024-10-10 12:02:10 +03:00
Nikolai Petukhov
6096bdc4cb done 2024-10-10 12:01:49 +03:00
Askar Akhmetkhanov
dd10b080e8 0.4.0 2024-10-09 17:38:38 +03:00
Askar Akhmetkhanov
4cf909c607 Chat, scrolling, sockets and timestamps updated 2024-10-09 17:37:50 +03:00
Nikolai Petukhov
13f4d43761 chats and messages 2024-10-05 11:46:18 +03:00
Nikolai Petukhov
22a549e269 an 2024-10-04 16:06:48 +03:00
Nikolai Petukhov
25c3e16c74 changing fields in account is done and upd chats retrieval 2024-10-04 15:53:50 +03:00
Nikolai Petukhov
86db5df813 chat retrieval is done 2024-10-04 14:29:00 +03:00
Nikolai Petukhov
d1e824ab77 retrieving chats 2024-10-04 11:21:21 +03:00
Nikolai Petukhov
073c61977f init for chats and api 2024-10-04 00:06:44 +03:00
Nikolai Petukhov
1301c145e8 check json format 2024-10-03 23:20:40 +03:00
Nikolai Petukhov
ac6dffa129 check json format 2024-10-03 23:12:38 +03:00
Nikolai Petukhov
8e4cad4c85 check json format 2024-10-03 23:06:18 +03:00
Nikolai Petukhov
d1091e570b check json format 2024-10-03 23:01:38 +03:00
Nikolai Petukhov
4a5041a65e check json format 2024-10-03 22:48:57 +03:00
Nikolai Petukhov
8d0fadc906 change name action for api 2024-10-03 22:35:39 +03:00
Nikolai Petukhov
6bea0428f4 api link fix 2024-10-03 21:26:02 +03:00
Nikolai Petukhov
660f2e9d5c 0.3.0 2024-10-03 21:16:23 +03:00
Nikolai Petukhov
a9b683797b auth with api 2024-10-03 21:15:48 +03:00
Nikolai Petukhov
a3484f4525 backend init 2024-09-28 12:51:59 +03:00
Nikolai Petukhov
876ef28221 0.2.4 2024-09-28 10:37:42 +03:00
Nikolai Petukhov
e6231f86b4 account page is done 2024-09-28 10:34:06 +03:00
Nikolai Petukhov
9d13f571d9 0.2.3 2024-09-28 10:00:38 +03:00
Nikolai Petukhov
f654851e1a fix dependencies 2024-09-28 09:56:43 +03:00
Askar Akhmetkhanov
3fb107fd8b Commit 2024-09-28 00:12:00 +03:00
Nikolai Petukhov
2a881f3920 small fix 2024-09-21 17:25:03 +03:00
42 changed files with 2819 additions and 172 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
TOKEN_KEY=5frv12e4few3r

View File

@@ -23,5 +23,6 @@ module.exports = {
},
config: {
"enterfront.api": "/api",
// paste stand URL to config
},
};

694
package-lock.json generated
View File

@@ -1,25 +1,33 @@
{
"name": "enterfront",
"version": "0.2.2",
"version": "0.5.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "enterfront",
"version": "0.2.2",
"version": "0.5.1",
"dependencies": {
"@brojs/cli": "^1.0.0",
"@brojs/create": "^1.0.0",
"@ijl/cli": "^5.1.0",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"dotenv": "^16.4.5",
"emoji-mart": "^5.6.0",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-emoji-picker": "^1.0.13",
"react-icons": "^5.3.0",
"react-router-dom": "^6.26.1",
"react-toastify": "^10.0.5",
"socket.io": "^4.8.0",
"socket.io-client": "^4.8.0",
"styled-components": "^6.1.13",
"typescript": "^5.5.4"
"typescript": "^5.5.4",
"ws": "^8.18.0"
}
},
"node_modules/@ampproject/remapping": {
@@ -1848,9 +1856,9 @@
}
},
"node_modules/@brojs/cli/node_modules/i18next": {
"version": "23.15.1",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.15.1.tgz",
"integrity": "sha512-wB4abZ3uK7EWodYisHl/asf8UYEhrI/vj/8aoSsrj/ZDxj4/UXPOa1KvFt1Fq5hkUHquNqwFlDprmjZ8iySgYA==",
"version": "23.15.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.15.2.tgz",
"integrity": "sha512-zcPSWzCvw6uKnuYHIqs4W7hTuB9e3AFcSdZgvCWoPXIZsBjBd4djN2/2uOHIB+1DFFkQnMBXvhNg7J3WyCuywQ==",
"funding": [
{
"type": "individual",
@@ -2686,9 +2694,10 @@
}
},
"node_modules/@ijl/cli/node_modules/@types/react": {
"version": "17.0.80",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.80.tgz",
"integrity": "sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==",
"version": "17.0.83",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.83.tgz",
"integrity": "sha512-l0m4ArKJvmFtR4e8UmKrj1pB4tUgOhJITf+mADyF/p69Ts1YAR/E+G9XEM0mHXKVRa1dQNHseyyDNzeuAXfXQw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
@@ -2714,6 +2723,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
@@ -2727,6 +2737,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
@@ -2769,6 +2780,7 @@
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
@@ -3061,12 +3073,14 @@
"node_modules/@types/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
"license": "MIT"
},
"node_modules/@types/cors": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
"integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
@@ -3132,6 +3146,7 @@
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
"license": "MIT",
"optional": true,
"peer": true
},
@@ -3458,6 +3473,15 @@
"ajv": "^8.8.2"
}
},
"node_modules/amdefine": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
"integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==",
"license": "BSD-3-Clause OR MIT",
"engines": {
"node": ">=0.4.2"
}
},
"node_modules/ansi-html-community": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz",
@@ -3559,6 +3583,12 @@
"node": ">=0.10.0"
}
},
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"license": "MIT"
},
"node_modules/assign-symbols": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
@@ -3567,6 +3597,15 @@
"node": ">=0.10.0"
}
},
"node_modules/ast-types": {
"version": "0.9.6",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz",
"integrity": "sha512-qEdtR2UH78yyHX/AUNfXmJTlM48XoFZKBdwi1nzkI1mJL21cmbu0cvjxjpkXJ5NENMq42H+hNs8VLJcqXLerBQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -3687,10 +3726,20 @@
"node": ">=0.10.0"
}
},
"node_modules/base62": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/base62/-/base62-1.2.8.tgz",
"integrity": "sha512-V6YHUbjLxN1ymqNLb1DPHoU1CpfdL7d2YTIp5W3U4hhoG4hhxNmsFDs66M9EXxBiSEke5Bt5dwdfMwwZF70iLA==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"license": "MIT",
"engines": {
"node": "^4.5.0 || >= 5.9"
}
@@ -3802,6 +3851,12 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -4037,6 +4092,15 @@
"node": ">=6"
}
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/collection-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@@ -4094,6 +4158,64 @@
"resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz",
"integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w=="
},
"node_modules/commoner": {
"version": "0.10.8",
"resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.8.tgz",
"integrity": "sha512-3/qHkNMM6o/KGXHITA14y78PcfmXh4+AOCJpSoF73h4VY1JpdGv3CHMS5+JW6SwLhfJt4RhNmLAa7+RRX/62EQ==",
"license": "MIT",
"dependencies": {
"commander": "^2.5.0",
"detective": "^4.3.1",
"glob": "^5.0.15",
"graceful-fs": "^4.1.2",
"iconv-lite": "^0.4.5",
"mkdirp": "^0.5.0",
"private": "^0.1.6",
"q": "^1.1.2",
"recast": "^0.11.17"
},
"bin": {
"commonize": "bin/commonize"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commoner/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
"node_modules/commoner/node_modules/glob": {
"version": "5.0.15",
"resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
"integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"license": "ISC",
"dependencies": {
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "2 || 3",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
}
},
"node_modules/commoner/node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"license": "MIT",
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/component-emitter": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
@@ -4224,6 +4346,13 @@
"webpack": "^5.1.0"
}
},
"node_modules/core-js": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==",
"deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
"license": "MIT"
},
"node_modules/core-js-compat": {
"version": "3.38.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz",
@@ -4240,6 +4369,7 @@
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
@@ -4437,6 +4567,15 @@
"node": ">=0.10.0"
}
},
"node_modules/defined": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz",
"integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/del": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz",
@@ -4531,6 +4670,28 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/detective": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz",
"integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==",
"license": "MIT",
"dependencies": {
"acorn": "^5.2.1",
"defined": "^1.0.0"
}
},
"node_modules/detective/node_modules/acorn": {
"version": "5.7.4",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz",
"integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -4547,6 +4708,18 @@
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
},
"node_modules/dotenv": {
"version": "16.4.5",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@@ -4557,6 +4730,15 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -4567,6 +4749,24 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.18.tgz",
"integrity": "sha512-1OfuVACu+zKlmjsNdcJuVQuVE61sZOLbNM4JAQ1Rvh6EOj0/EUKhMJjRH73InPlXSh8HIJk1cVZ8pyOV/FMdUQ=="
},
"node_modules/emoji-annotation-to-unicode": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/emoji-annotation-to-unicode/-/emoji-annotation-to-unicode-0.2.4.tgz",
"integrity": "sha512-0GAasay1KK/a+Vns0aK1EwRMdLTvvvHO5Jc0SFwfwVhOhroIyT1IqBT8igZFrn3aw0u2b1LRshNsofkiL3jCkA==",
"license": "MIT"
},
"node_modules/emoji-emoticon-to-unicode": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/emoji-emoticon-to-unicode/-/emoji-emoticon-to-unicode-0.2.2.tgz",
"integrity": "sha512-fZ/wYomR+zc1/Sp/uY85M06XaGr+P+mH/uzh4l3rPPnv3YXrF3W2Cm/IqWcXvMw8m65/vmVvigBVqvkUfPJi4A==",
"license": "MIT"
},
"node_modules/emoji-mart": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz",
"integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==",
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -4589,9 +4789,10 @@
}
},
"node_modules/engine.io": {
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
"integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.1.tgz",
"integrity": "sha512-NEpDCw9hrvBW+hVEOK4T7v0jFJ++KgtPl4jKFwsZVfG1XhS0dCrSb3VMb9gPAd7VAdW52VT1EnaNiU2vM8C0og==",
"license": "MIT",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
@@ -4608,6 +4809,40 @@
"node": ">=10.2.0"
}
},
"node_modules/engine.io-client": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.1.tgz",
"integrity": "sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-client/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
@@ -4620,10 +4855,32 @@
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/engine.io/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/enhanced-resolve": {
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
@@ -4644,6 +4901,19 @@
"node": ">=6"
}
},
"node_modules/envify": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/envify/-/envify-3.4.1.tgz",
"integrity": "sha512-XLiBFsLtNF0MOZl+vWU59yPb3C2JtrQY2CNJn22KH75zPlHWY5ChcAQuf4knJeWT/lLkrx3sqvhP/J349bt4Bw==",
"license": "MIT",
"dependencies": {
"jstransform": "^11.0.3",
"through": "~2.3.4"
},
"bin": {
"envify": "bin/envify"
}
},
"node_modules/envinfo": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz",
@@ -4721,6 +4991,31 @@
"node": ">=8.0.0"
}
},
"node_modules/esprima": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
"integrity": "sha512-AWwVMNxwhN8+NIPQzAQZCm7RkLC4RbM3B1OobMuyp3i+w73X57KCKaVIxaRZb+DYCojq7rspo+fmuQfAboyhFg==",
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/esprima-fb": {
"version": "15001.1.0-dev-harmony-fb",
"resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz",
"integrity": "sha512-59dDGQo2b3M/JfKIws0/z8dcXH2mnVHkfSPRhCYS91JNGfGNwr7GsSF6qzWZuOGvw5Ii0w9TtylrX07MGmlOoQ==",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
@@ -5065,6 +5360,19 @@
"reusify": "^1.0.4"
}
},
"node_modules/fbjs": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.6.1.tgz",
"integrity": "sha512-4KW7tT33ytfazK3Ekvesbsa4A5J79hUrdXONQGZ0wM6i3PFc70YknF9kj1eyx3mDupgJ7Z+ifFhcMJ+ps2eZIw==",
"license": "BSD-3-Clause",
"dependencies": {
"core-js": "^1.0.0",
"loose-envify": "^1.0.0",
"promise": "^7.0.3",
"ua-parser-js": "^0.7.9",
"whatwg-fetch": "^0.9.0"
}
},
"node_modules/filesize": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz",
@@ -6468,6 +6776,101 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"license": "MIT",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jsonwebtoken/node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/jstransform": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/jstransform/-/jstransform-11.0.3.tgz",
"integrity": "sha512-LGm87w0A8E92RrcXt94PnNHkFqHmgDy3mKHvNZOG7QepKCTCH/VB6S+IEN+bT4uLN3gVpOT0vvOOVd96osG71g==",
"license": "BSD-3-Clause",
"dependencies": {
"base62": "^1.1.0",
"commoner": "^0.10.1",
"esprima-fb": "^15001.1.0-dev-harmony-fb",
"object-assign": "^2.0.0",
"source-map": "^0.4.2"
},
"bin": {
"jstransform": "bin/jstransform"
},
"engines": {
"node": ">=0.8.8"
}
},
"node_modules/jstransform/node_modules/object-assign": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz",
"integrity": "sha512-CdsOUYIh5wIiozhJ3rLQgmUTgcyzFwZZrqhkKhODMoGtPKM+wt0h0CNIoauJWMsS9822EdzPsF/6mb4nLvPN5g==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/jstransform/node_modules/source-map": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
"integrity": "sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A==",
"license": "BSD-3-Clause",
"dependencies": {
"amdefine": ">=0.0.4"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"license": "MIT",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/keygrip": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
@@ -6537,11 +6940,59 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash.compact": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/lodash.compact/-/lodash.compact-3.0.1.tgz",
"integrity": "sha512-2ozeiPi+5eBXW1CLtzjk8XQFhQOEMwwfxblqeq6EGyTxZJ1bPATqilY0e6g2SLQpP4KuMeuioBhEnWz5Pr7ICQ==",
"license": "MIT"
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"license": "MIT"
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
"license": "MIT"
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"license": "MIT"
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
"license": "MIT"
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -7342,6 +7793,15 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"node_modules/private": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
"integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -7350,6 +7810,15 @@
"node": ">= 0.6.0"
}
},
"node_modules/promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"license": "MIT",
"dependencies": {
"asap": "~2.0.3"
}
},
"node_modules/prompts": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz",
@@ -7398,6 +7867,17 @@
"node": ">=6"
}
},
"node_modules/q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
"deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)",
"license": "MIT",
"engines": {
"node": ">=0.6.0",
"teleport": ">=0.2.0"
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
@@ -7717,6 +8197,60 @@
"react": "^18.3.1"
}
},
"node_modules/react-emoji": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/react-emoji/-/react-emoji-0.4.4.tgz",
"integrity": "sha512-NQUk+QgF3af2poGioW9Dv6kQg9nJCEZVxRsYRYLa1H2XTelEkLVMeeTNU8Y5cMjQRuVnAfl7TzJ4zSix/qxDiQ==",
"license": "MIT",
"dependencies": {
"emoji-annotation-to-unicode": "^0.2.4",
"emoji-emoticon-to-unicode": "^0.2.2",
"escape-string-regexp": "^1.0.3",
"lodash.compact": "^3.0.0",
"object-assign": "^2.0.0"
}
},
"node_modules/react-emoji-picker": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/react-emoji-picker/-/react-emoji-picker-1.0.13.tgz",
"integrity": "sha512-nLcVgNb3Iok9Jy5frAPJoflAbHKg9Df+UZuhYVZ3bK12A2k9nFzmQyI2apTvMPC2r4h/KpHepfK10siMdqKR4g==",
"license": "ISC",
"dependencies": {
"react": "^0.14.3",
"react-emoji": "^0.4.1"
}
},
"node_modules/react-emoji-picker/node_modules/react": {
"version": "0.14.10",
"resolved": "https://registry.npmjs.org/react/-/react-0.14.10.tgz",
"integrity": "sha512-yxMw5aorZG4qsLVBfjae4wGFvd5708DhcxaXLJ3IOTgr1TCs8k9+ZheGgLGr5OfwWMhSahNbGvvoEDzrxVWouA==",
"license": "BSD-3-Clause",
"dependencies": {
"envify": "^3.0.0",
"fbjs": "^0.6.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-emoji/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/react-emoji/node_modules/object-assign": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz",
"integrity": "sha512-CdsOUYIh5wIiozhJ3rLQgmUTgcyzFwZZrqhkKhODMoGtPKM+wt0h0CNIoauJWMsS9822EdzPsF/6mb4nLvPN5g==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-error-overlay": {
"version": "6.0.11",
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
@@ -7784,6 +8318,19 @@
"react-dom": ">=16.8"
}
},
"node_modules/react-toastify": {
"version": "10.0.5",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz",
"integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==",
"license": "MIT",
"dependencies": {
"clsx": "^2.1.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -7796,6 +8343,30 @@
"node": ">=8.10.0"
}
},
"node_modules/recast": {
"version": "0.11.23",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz",
"integrity": "sha512-+nixG+3NugceyR8O1bLU45qs84JgI3+8EauyRZafLgC9XbdAOIVgwV1Pe2da0YzGo62KzWoZwUpVEQf6qNAXWA==",
"license": "MIT",
"dependencies": {
"ast-types": "0.9.6",
"esprima": "~3.1.0",
"private": "~0.1.5",
"source-map": "~0.5.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/recast/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==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/rechoir": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
@@ -8430,15 +9001,16 @@
}
},
"node_modules/socket.io": {
"version": "4.7.5",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz",
"integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==",
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz",
"integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.5.2",
"engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
@@ -8455,6 +9027,42 @@
"ws": "~8.17.1"
}
},
"node_modules/socket.io-adapter/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/socket.io-client": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.0.tgz",
"integrity": "sha512-C0jdhD5yQahMws9alf/yvtsMGTaIDBnZ8Rb5HU56svyq0l5LIrGzIDZZD5pHQlmzxLuU91Gz+VpQMKgCTNYtkw==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.6.1",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
@@ -8867,6 +9475,12 @@
"tslib": "^2"
}
},
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"license": "MIT"
},
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -8996,6 +9610,32 @@
"node": ">=14.17"
}
},
"node_modules/ua-parser-js": {
"version": "0.7.39",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.39.tgz",
"integrity": "sha512-IZ6acm6RhQHNibSt7+c09hhvsKy9WUr4DVbeq9U8o71qxyYtJpQeDxQnMrVqnIFMLcQjHO0I9wgfO2vIahht4w==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
},
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
}
],
"license": "MIT",
"bin": {
"ua-parser-js": "script/cli.js"
},
"engines": {
"node": "*"
}
},
"node_modules/uglify-js": {
"version": "3.19.3",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
@@ -9459,6 +10099,11 @@
"node": ">=6"
}
},
"node_modules/whatwg-fetch": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz",
"integrity": "sha512-DIuh7/cloHxHYwS/oRXGgkALYAntijL63nsgMQsNSnBj825AysosAqA2ZbYXGRqpPRiNH7335dTqV364euRpZw=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -9540,9 +10185,10 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
@@ -9559,6 +10205,14 @@
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz",
"integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@@ -5,13 +5,21 @@
"@ijl/cli": "^5.1.0",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"dotenv": "^16.4.5",
"emoji-mart": "^5.6.0",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-emoji-picker": "^1.0.13",
"react-icons": "^5.3.0",
"react-router-dom": "^6.26.1",
"react-toastify": "^10.0.5",
"socket.io": "^4.8.0",
"socket.io-client": "^4.8.0",
"styled-components": "^6.1.13",
"typescript": "^5.5.4"
"typescript": "^5.5.4",
"ws": "^8.18.0"
},
"main": "./src/index.tsx",
"scripts": {
@@ -21,5 +29,5 @@
"clean": "rimraf dist"
},
"name": "enterfront",
"version": "0.2.2"
"version": "0.5.1"
}

View File

@@ -1,15 +1,39 @@
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import React, {useEffect} from 'react';
import { BrowserRouter } from 'react-router-dom';
import { Dashboard } from './dashboard';
import {ToastContainer} from "react-toastify";
import 'react-toastify/dist/ReactToastify.css';
import './index.css'
import {displayMessage} from "./backend/notifications/notifications.js";
import {MessageType} from "./backend/notifications/message";
const App = () => {
useEffect(() => {
document.title = 'Enterfront';
}, []);
useEffect(() => {
const msg = localStorage.getItem('message');
if (!msg) return;
displayMessage(msg, MessageType.SUCCESS);
localStorage.removeItem('message');
}, []);
return(
<BrowserRouter>
<Dashboard />
</BrowserRouter>
<div>
<BrowserRouter>
<Dashboard />
</BrowserRouter>
<ToastContainer/>
</div>
)
}

60
src/backend/api.js Normal file
View File

@@ -0,0 +1,60 @@
import {getConfigValue} from "@brojs/cli";
const LOCAL = "http://localhost:8099";
const DEV = "https://dev.bro-js.ru";
export const BASE_API_URL = DEV + getConfigValue("enterfront.api");
// fetch(`${BASE_API_URL}/books/list`)
export async function post(path, body) {
const token = localStorage.getItem('token');
const res = await fetch(`${BASE_API_URL}${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": token ? `Bearer ${token}` : undefined
},
body: JSON.stringify(body)
});
console.log("Initial data from API:", res)
const data = JSON.parse(await res.text());
console.log("Data from API:", data)
if (res.status === 200) {
console.log("Received post:", data);
return {ok: true, data: data};
} else {
console.log("Error during post:", data.message);
return {ok: false, data: data};
}
}
export async function get(path){
const token = localStorage.getItem('token');
const res = await fetch(`${BASE_API_URL}${path}`, {
method: "GET",
headers: {
"Authorization": token ? `Bearer ${token}` : undefined
}
});
console.log("Data from API:", res)
const data = await res.json();
if (res.status === 200) {
console.log("Received get:", data);
return {ok: true, data: data};
} else {
console.log("Error during get:", data.message);
return {ok: false, data: data};
}
}

View File

@@ -1,8 +0,0 @@
export default class Interlocutor {
constructor(id, name) {
this.name = name
this.id = id
}
static name;
static id;
}

View File

@@ -0,0 +1,6 @@
export enum MessageType {
ERROR,
SUCCESS,
INFO,
WARN
}

View File

@@ -0,0 +1,28 @@
import {toast} from "react-toastify";
import {MessageType} from "./message.tsx";
export const displayMessage = (message, type) => {
switch (type) {
default:
case MessageType.ERROR:
toast.error(message, {
position: 'bottom-right',
});
break;
case MessageType.INFO:
toast.info(message, {
position: 'bottom-right',
});
break;
case MessageType.SUCCESS:
toast.success(message, {
position: 'bottom-right',
});
break;
case MessageType.WARN:
toast.warn(message, {
position: 'bottom-right',
});
break;
}
}

31
src/backend/server.js Normal file
View File

@@ -0,0 +1,31 @@
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
require("dotenv").config();
const app = express();
const server = http.createServer(app);
const io = new Server(server);
io.on("connection", (socket) => {
console.log("New connection:", socket.id);
// For messages
socket.on("sendMessage", (message) => {
console.log("Message received:", message);
socket.broadcast.emit("receiveMessage", message);
});
socket.on("disconnect", () => {
console.log("User disconnected:", socket.id);
});
});
app.get("/", (req, res) => {
res.send("Socket.IO Server is running");
});
const PORT = process.env.PORT || 8099;
server.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

View File

@@ -1,3 +0,0 @@
export default class User {
}

View File

@@ -0,0 +1,51 @@
import React, {useState} from 'react';
import { URLs } from "../../__data__/urls";
import ActionButton from "./ActionButton.jsx";
import InputField from "../reg/InputField.jsx";
const AccountButtons = (props) => {
const [chName, setChName] = useState(false);
const [chPassword, setChPassword] = useState(false);
const [nickname, setNickname] = useState("");
const [password, setPassword] = useState("");
return (
<div className="account-buttons">
{props.registered ? (
<>
<ActionButton title={"Exit"} action={props.exitHandler}/>
<ActionButton title={"Change Name"} action={() => setChName(true)}/>
{chName ? (
<InputField
title={""}
value={nickname}
setValue={setNickname}
placeholder='Enter your new nickname'
submit={nickname}
enter={props.changeNameHandler}
/>
) : null}
<ActionButton title={"Change Pass"} action={() => setChPassword(true)}/>
{chPassword ? (
<div>
<InputField
title={""}
value={password}
setValue={setPassword}
placeholder='Enter your new password'
submit={password}
enter={props.changePassHandler}
/>
</div>
) : null}
</>
) : null}
<a className="MyButton mclaren-regular" href={URLs.home.url}>Back</a>
</div>
);
};
export default AccountButtons;

View File

@@ -0,0 +1,11 @@
import React from 'react';
const ActionButton = (props) => {
return (
<a className="MyButton mclaren-regular" onClick={() => {
props.action()
}}>{props.title}</a>
);
};
export default ActionButton;

View File

@@ -0,0 +1,18 @@
import React from 'react';
const HelloItem = (props) => {
return (
<div className="hello-item-class">
{!!props.nickname ? (
<>
<h1 className="mclaren-regular">Hello, {props.nickname}!</h1>
<p className="mclaren-regular">Your ID: {props.id}</p>
</>
) : (
<p className="mclaren-regular">You don't have an account :(</p>
)}
</div>
);
};
export default HelloItem;

View File

@@ -0,0 +1,54 @@
.account-buttons {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-bottom: 30px;
}
.account-buttons a {
display: flex;
font-size: 1.5vw;
transition: color 0.2s ease-in;
width: 20vw;
margin-top: 2vw;
}
.account-buttons a:hover {
color: black;
}
.hello-item-class {
display: flex;
flex-direction: column;
align-items: center;
}
.hello-item-class h1 {
font-size: 4vw;
margin-bottom: 0;
}
.hello-item-class p {
font-size: 1.5vw;
}
@media only screen and (max-width: 800px) {
.account-buttons a {
font-size: 2.5vh;
width: 60vw;
margin-top: 3vh;
}
.hello-item-class h1 {
font-size: 5vh;
}
.hello-item-class p {
font-size: 2vh;
}
}

View File

@@ -43,6 +43,10 @@ const Card = (props) => {
transition: color 0.3s ease-in;
}
h2 {
margin-top: 0;
}
&:hover {
box-shadow: 0 10px 40px rgb(${hexToRgb(props.color)});
cursor: pointer;
@@ -56,7 +60,7 @@ const Card = (props) => {
flex-direction: column;
border-radius: 2vh;
padding: 3vw;
padding: 5vw;
}
`

View File

@@ -1,40 +1,92 @@
import React from 'react';
import React, { useEffect, useState } from "react";
import Card from "./Card.jsx";
import { get } from "../../backend/api";
import { displayMessage } from "../../backend/notifications/notifications";
import { MessageType } from "../../backend/notifications/message";
const ChatsList = (props) => {
const { chats } = props;
const { chats } = props;
const [customChats, setCustomChats] = useState([]);
const colorMap = {
orange: 'FFA500FF',
aqua: '00FFFFFF',
crimson: 'DC143CFF',
red: 'FF0000FF',
violet: '8A2BE2FF',
seagreen: '20B2AAFF',
green: 'ADFF2FFF',
blue: '0000FFFF',
pink: 'FF1493FF',
cyan: '72FAFAFF'
}
const updateList = async () => {
const username = localStorage.getItem("username");
if (!username) return null;
function getColor(chatId) {
const keys = Object.keys(colorMap);
const index = chatId % keys.length;
return colorMap[keys[index]];
}
const updatedChats = await Promise.all(
chats.map(async (chat) => {
const interlocutorId = chat.id1 === username ? chat.id2 : chat.id1;
const { ok, data } = await get("/auth/" + interlocutorId);
if (!ok) {
displayMessage(data.message, MessageType.ERROR);
return null;
}
const interlocutor = data.user;
const lastMessage =
chat.messages.length > 0
? chat.messages[chat.messages.length - 1]
: { data: "", timestamp: new Date(0).toISOString() };
return {
id: interlocutorId,
name: interlocutor.nickname,
lastMessageData: lastMessage.data,
lastMessageTimestamp: lastMessage.timestamp,
};
})
);
const validChats = updatedChats.filter((chat) => chat !== null);
validChats.sort(
(a, b) =>
new Date(b.lastMessageTimestamp) - new Date(a.lastMessageTimestamp)
);
setCustomChats(validChats);
};
useEffect(() => {
updateList().then();
}, [chats]);
const colorMap = {
orange: "FFA500FF",
aqua: "00FFFFFF",
crimson: "DC143CFF",
red: "FF0000FF",
violet: "8A2BE2FF",
seagreen: "20B2AAFF",
green: "ADFF2FFF",
blue: "0000FFFF",
pink: "FF1493FF",
cyan: "72FAFAFF",
};
function getColor(chatId) {
const keys = Object.keys(colorMap);
const numericId = Array.from(chatId).reduce(
(sum, char) => sum + char.charCodeAt(0),
0
);
const index = numericId % keys.length;
return colorMap[keys[index]];
}
return (
<div className="ChatsList">
{chats.map((item, index) => (
<Card
key={index}
name={item.name}
lastMessage={item.lastMessage}
id={item.id}
color={getColor(item.id)}
/>
))}
</div>
<div className="ChatsList">
{customChats.map((item, index) => (
<Card
key={index}
name={item.name}
lastMessage={item.lastMessageData}
id={item.id}
color={getColor(item.id)}
/>
))}
</div>
);
};

View File

@@ -0,0 +1,11 @@
import React from 'react';
const Search = (props) => {
return (
<a className="MyButton search-class mclaren-regular" onClick={() => {
props.search(props.item);
}}>Find</a>
);
};
export default Search;

View File

@@ -34,6 +34,8 @@
margin-left: 4vw;
margin-right: 4vw;
padding-top: 0;
}
.ChatsList {

View File

@@ -12,11 +12,30 @@
color: orange;
}
.search-class {
margin-top: 2vw;
margin-bottom: 4vw;
display: flex;
align-items: center;
}
.search-input div input {
background-color: white;
color: black;
border: 3px solid black;
}
@media only screen and (max-width: 800px) {
.homeTitle {
font-size: 8vh;
}
.search-class {
margin-top: 3vh;
}
}
.chatIcon {

View File

@@ -1,6 +1,7 @@
@import url("reg/index.css");
@import url("init/index.css");
@import url("home/index.css");
@import url("account/index.css");
.MyButton {
text-decoration: none;

View File

@@ -17,3 +17,16 @@
font-weight: bold;
}
@media only screen and (max-width: 800px) {
.MyButton.singleNavButton p {
font-size: 2.5vh;
}
.MyButton.singleNavButton {
min-width: 60vw;
border-radius: 30vh;
margin-top: 3vh;
}
}

View File

@@ -1,6 +1,7 @@
import React from 'react';
const InputField = (props) => {
console.log('class:', props.className)
return (
<div>
<p>{props.title}</p>
@@ -8,6 +9,14 @@ const InputField = (props) => {
onChange={(e) => props.setValue(e.target.value)}
value={props.value}
className="Input"
placeholder={(props.placeholder) ? props.placeholder : ''}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (props.submit) {
props.enter(props.submit);
}
}
}}
/>
</div>
);

View File

@@ -1,25 +1,25 @@
import React from 'react';
import { Routes, Route } from 'react-router-dom';
import React from "react";
import { Routes, Route } from "react-router-dom";
import { URLs } from './__data__/urls';
import { URLs } from "./__data__/urls";
import Home from './pages/Home.jsx'
import Init from './pages/Init.jsx'
import Account from './pages/Account.jsx'
import Chat from './pages/Chat.jsx'
import SignIn from './pages/SignIn.jsx'
import SignUp from './pages/SignUp.jsx'
import Home from "./pages/Home.jsx";
import Init from "./pages/Init.jsx";
import Account from "./pages/Account.jsx";
import Chat from "./pages/Chat.jsx";
import SignIn from "./pages/SignIn.jsx";
import SignUp from "./pages/SignUp.jsx";
export const Dashboard = () => {
return (
<Routes>
<Route path={URLs.baseUrl} element={<Init/>}/>
<Route path={URLs.home.url} element={<Home/>}/>
<Route path={URLs.chat.url} element={<Chat/>}/>
<Route path={URLs.auth.url} element={<SignIn/>}/>
<Route path={URLs.reg.url} element={<SignUp/>}/>
<Route path={URLs.account.url} element={<Account/>}/>
<Route path="*" element={<h1>404 page not found</h1>}/>
<Route path={URLs.baseUrl} element={<Init />} />
<Route path={URLs.home.url} element={<Home />} />
<Route path={URLs.chat.url} element={<Chat />} />
<Route path={URLs.auth.url} element={<SignIn />} />
<Route path={URLs.reg.url} element={<SignUp />} />
<Route path={URLs.account.url} element={<Account />} />
<Route path="*" element={<h1>404 page not found</h1>} />
</Routes>
);
};

View File

@@ -1,9 +1,81 @@
import React from "react";
import React, {useEffect, useState} from "react";
import AccountButtons from "../components/account/AccountButtons.jsx";
import userIcon from "../../images/user.svg";
import {get, post} from "../backend/api";
import {displayMessage} from "../backend/notifications/notifications";
import {MessageType} from "../backend/notifications/message";
import HelloItem from "../components/account/HelloItem.jsx";
import { URLs } from "../__data__/urls";
const Account = () => {
return (
<h1>Account</h1>
)
}
const exitHandler = () => {
localStorage.removeItem("username");
localStorage.removeItem("token");
export default Account;
localStorage.setItem("message", "Exited successfully!");
window.location.href = "/";
}
const [nickname, setNickname] = useState("");
const [id, setId] = useState("");
async function changeNameHandler (newNickname) {
if (!newNickname) return;
const {ok, data} = await post('/change/nickname', {id: id, newNickname: newNickname});
if (!ok) {
displayMessage(data.message, MessageType.ERROR);
} else {
localStorage.setItem("message", "Name was changed");
window.location.href = URLs.account.url;
}
}
async function changePassHandler (newPass){
if (!newPass) return;
const {ok, data} = await post('/change/password', {id: id, newPassword: newPass});
if (!ok) {
displayMessage(data.message, MessageType.ERROR);
} else {
localStorage.setItem("message", "Password was changed");
window.location.href = URLs.account.url;
}
}
async function getUser() {
const username = localStorage.getItem("username");
if (!username) {
displayMessage("You're not logged in!", MessageType.WARN);
return;
}
const {ok, data} = await get('/auth/' + username);
if (!ok) {
displayMessage("Some error with auth:" + data.message, MessageType.ERROR);
return;
}
setNickname(data.user.nickname);
setId(username);
}
useEffect(() => {getUser().then()}, [])
return (
<div className="account-items">
<img src={userIcon} alt="user" />
<HelloItem nickname={nickname} id={id} />
<AccountButtons
exitHandler={exitHandler}
changeNameHandler={changeNameHandler}
changePassHandler={changePassHandler}
registered={!!nickname}
/>
</div>
);
};
export default Account;

View File

@@ -1,20 +1,242 @@
import React, {useEffect, useState} from 'react';
import React, { useEffect, useState, useRef } from "react";
import { useNavigate } from "react-router-dom";
import "./css/Chat.css";
import { FaPaperPlane, FaSmile } from "react-icons/fa";
import { get, post } from "../backend/api";
import { displayMessage } from "../backend/notifications/notifications";
import { MessageType } from "../backend/notifications/message";
import io from "socket.io-client";
const emojis = [
"😀",
"😁",
"😂",
"🤣",
"😃",
"😄",
"😅",
"😆",
"😉",
"😊",
"😋",
"😎",
"😍",
"😘",
"🥰",
"😗",
"😙",
"😚",
"🙂",
"🤗",
"🤩",
"🤔",
"😐",
"😑",
"😶",
"🙄",
"😏",
"😣",
"😥",
"😮",
"🤐",
"😯",
"😪",
"😫",
"😴",
"😌",
"😛",
"😜",
"🤪",
"🤨",
"😝",
"🤑",
"😒",
"😓",
"😔",
"😕",
"😖",
"😞",
"😟",
"😠",
"😡",
"🤬",
"😱",
"😨",
"😧",
"😇",
"🥳",
"🥺",
"😻",
"😼",
"😽",
"🙈",
"🙉",
"🙊",
"💀",
"👻",
"👽",
];
const Chat = () => {
const [interlocutorId, setInterlocutorId] = useState(0); // State to hold the interlocutorId
const [interlocutorId, setInterlocutorId] = useState("");
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState("");
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const socket = useRef(null);
const chatRef = useRef(null);
const navigate = useNavigate();
function getInterlocutorId() {
const id = localStorage.getItem('interlocutorId');
return id ? id : 0;
const [myId, setMyId] = useState("");
useEffect(() => {
const id = localStorage.getItem("interlocutorId");
setInterlocutorId(id);
const username = localStorage.getItem("username");
setMyId(username);
if (!id || !username) {
displayMessage("You are not logged in!", MessageType.WARN);
return () => {};
}
useEffect(() => {
const id = getInterlocutorId();
setInterlocutorId(id);
}, []);
socket.current = io("http://localhost:8099");
socket.current.on("receiveMessage", (message) => {
setMessages((prev) => [...prev, message]);
});
socket.current.on("connect_error", (err) => {
console.error("Connection Error:", err.message);
});
return () => {
socket.current.disconnect();
};
}, []);
useEffect(() => {
// retrieveMessages().then();
const interval = setInterval(() => {
retrieveMessages().then()
}, 2000);
return () => clearInterval(interval)
}, [myId, interlocutorId]);
useEffect(() => {
if (chatRef.current) {
chatRef.current.scrollTop = chatRef.current.scrollHeight;
}
}, [messages]);
async function sendMessageToDB(messageData) {
const { ok, data } = await post(
"/chat/message/" + myId + "/" + interlocutorId,
{ message: messageData }
);
if (!ok) {
displayMessage(data.message, MessageType.ERROR);
}
}
async function retrieveMessages() {
if (!myId || !interlocutorId) return;
const { ok, data } = await get("/chat/item/" + myId + "/" + interlocutorId);
if (!ok) {
displayMessage(data.message, MessageType.ERROR);
return;
}
setMessages(data.chat.messages);
}
const sendMessage = () => {
if (newMessage.trim()) {
const messageData = {
senderId: myId,
recipientId: interlocutorId,
data: newMessage,
timestamp: new Date().toLocaleString(),
};
socket.current.emit("sendMessage", messageData);
setMessages((prev) => [...prev, messageData]);
sendMessageToDB(messageData).then();
setNewMessage("");
}
};
const handleKeyPress = (e) => {
if (e.key === "Enter") {
sendMessage();
}
};
const handleEmojiSelect = (emoji) => {
setNewMessage((prev) => prev + emoji);
setShowEmojiPicker(false);
};
return (
<h2>Chat with ... (id = {interlocutorId})</h2>
<div className="chat-container">
<div className="chat-header">
<h2>Chat with ... (id = {interlocutorId})</h2>
<button
onClick={() => navigate("/enterfront/home")}
className="home-button"
>
Home
</button>
</div>
<div className="chat-messages" ref={chatRef}>
{messages.map((msg, index) => (
<div
key={index}
className={`message-bubble ${
msg.senderId === myId ? "sent" : "received"
}`}
>
<div className="message-content">
<b>{msg.senderId === myId ? "You" : "They"}:</b> {msg.data}
</div>
<span className="message-timestamp">{msg.timestamp}</span>
</div>
))}
</div>
<div className="chat-input-container">
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
placeholder="Type a message..."
className="chat-input"
onKeyDown={handleKeyPress}
/>
<button
className="emoji-button"
onClick={() => setShowEmojiPicker((prev) => !prev)}
>
<FaSmile />
</button>
<button onClick={sendMessage} className="send-button">
<FaPaperPlane />
</button>
{showEmojiPicker && (
<div className="emoji-picker">
{emojis.map((emoji, index) => (
<span
key={index}
className="emoji"
onClick={() => handleEmojiSelect(emoji)}
style={{ cursor: "pointer", padding: "5px" }}
>
{emoji}
</span>
))}
</div>
)}
</div>
</div>
);
};

View File

@@ -1,76 +1,86 @@
import React from "react";
import React, { useEffect, useState } from "react";
import HomeTitle from "../components/home/HomeTitle.jsx";
import ChatsList from "../components/home/ChatsList.jsx";
import Header from "../components/home/Header.jsx";
import { displayMessage } from "../backend/notifications/notifications";
import { MessageType } from "../backend/notifications/message";
import { get, post } from "../backend/api";
import InputField from "../components/reg/InputField.jsx";
import Search from "../components/home/Search.jsx";
import { URLs } from "../__data__/urls";
const Home = () => {
const [chats, setChats] = useState([]);
const [interlocutor, setInterlocutor] = useState("");
// temp for testing
const chats = [
{
name: "Alice Johnson",
id: 123456,
lastMessage: "See you later!"
},
{
name: "Bob Smith",
id: 654321,
lastMessage: "Got it, thanks!"
},
{
name: "Charlie Brown",
id: 234567,
lastMessage: "How's the project going? How's the project going? How's the project going?" +
"How's the project going? How's the project going?"
},
{
name: "David Clark",
id: 765432,
lastMessage: "I'll send the files."
},
{
name: "Eve Adams",
id: 345678,
lastMessage: "Let's meet tomorrow."
},
{
name: "Frank Wright",
id: 876543,
lastMessage: "Can you review this?"
},
{
name: "Grace Lee",
id: 456789,
lastMessage: "Thanks for your help!"
},
{
name: "Hannah Scott",
id: 987654,
lastMessage: "See you at the meeting."
},
{
name: "Ian Davis",
id: 567890,
lastMessage: "Let me know when you're free."
},
{
name: "Jill Thompson",
id: 678901,
lastMessage: "I'll catch up with you later."
}
];
async function retrieveChats() {
const username = localStorage.getItem("username");
if (!username) {
displayMessage("You're not logged in!", MessageType.WARN);
return;
}
return (
<div className="homeWrapper">
<div className="headerPos">
<Header/>
</div>
const { ok, data } = await get("/chat/list/" + username);
if (!ok) {
displayMessage(data.message, MessageType.ERROR);
return;
}
<HomeTitle/>
<p>Your chats</p>
<ChatsList chats={chats} />
const sortedChats = data.chats.sort((a, b) => {
const lastMessageA = new Date(a.lastMessageTimestamp);
const lastMessageB = new Date(b.lastMessageTimestamp);
return lastMessageB - lastMessageA;
});
setChats(sortedChats);
}
async function createChat(alias) {
const username = localStorage.getItem("username");
if (!username) {
displayMessage("You're not logged in!", MessageType.WARN);
return;
}
displayMessage("Sent", MessageType.INFO);
const { ok, data } = await post("/chat/item/" + username + "/" + alias);
if (!ok) {
displayMessage(data.message, MessageType.ERROR);
} else {
localStorage.setItem("message", "Successfully opened chat!");
localStorage.setItem("interlocutorId", alias);
window.location.href = URLs.chat.url;
}
}
useEffect(() => {
retrieveChats();
}, []);
return (
<div className="homeWrapper">
<div className="headerPos">
<Header />
</div>
)
}
export default Home
<HomeTitle />
<div className="search-input">
<InputField
title="Create new chat"
value={interlocutor}
setValue={setInterlocutor}
placeholder="Enter the username (id)"
/>
</div>
<Search search={createChat} item={interlocutor} />
<p>Your chats</p>
<ChatsList chats={chats} />
</div>
);
};
export default Home;

View File

@@ -3,12 +3,41 @@ import InputField from "../components/reg/InputField.jsx";
import LoginButtons from "../components/reg/LoginButtons.jsx";
import LoginTitle from "../components/reg/loginTitle.jsx";
import {MessageType} from "../backend/notifications/message.tsx";
import {displayMessage} from "../backend/notifications/notifications.js";
import {post} from "../backend/api.js";
const SignIn = () => {
const [name, setName] = useState("");
const [password, setPassword] = useState("");
const submit = (e) => {
console.log('Sign In!')
const [nameErrorsCounter, setNameErrorsCounter] = useState(0);
async function submit() {
console.log('Sign In!')
const {ok, data} = await post('/auth/login', {name: name, password: password});
if (!ok) {
displayMessage(data.message, MessageType.ERROR);
if (nameErrorsCounter >= 1) {
displayMessage("Note that you need to enter your ID name", MessageType.INFO);
setNameErrorsCounter(0);
} else {
setNameErrorsCounter(nameErrorsCounter + 1);
}
return;
}
localStorage.setItem('token', data.token);
localStorage.setItem('username', name);
setNameErrorsCounter(0);
localStorage.setItem('message', 'Successfully logged in!');
window.location.href = "/";
}
return (

View File

@@ -2,20 +2,59 @@ import React, {useState} from 'react';
import InputField from "../components/reg/InputField.jsx";
import LoginButtons from "../components/reg/LoginButtons.jsx";
import LoginTitle from "../components/reg/loginTitle.jsx";
import {post} from "../backend/api";
import {displayMessage} from "../backend/notifications/notifications";
import {MessageType} from "../backend/notifications/message";
const SignUp = () => {
const [name, setName] = useState("");
const [nickname, setNickname] = useState("");
const [password, setPassword] = useState("");
const [repeatPassword, setRepeatPassword] = useState("");
const submit = (e) => {
console.log('Sign Up!')
async function login(name, password) {
const {ok, data} = await post('/auth/login', {name: name, password: password});
return {loginStatus: ok, loginData: data};
}
async function submit () {
console.log('Sign Up!');
if (password !== repeatPassword) {
displayMessage("Passwords don't match", MessageType.WARN);
return;
}
const {ok, data} = await post('/auth/reg',
{name: name, password: password, nickname: nickname});
if (!ok) {
displayMessage(data.message, MessageType.ERROR);
return;
}
const { loginStatus, loginData } = await login(name, password);
console.log(loginStatus, loginData)
if (!loginStatus) {
displayMessage(loginData.message, MessageType.ERROR);
return;
}
localStorage.setItem('token', loginData.token);
localStorage.setItem('username', name);
localStorage.setItem('message', 'Successfully signed up!');
window.location.href = "/";
}
return (
<div className="LoginList">
<LoginTitle/>
<InputField title="Name" setValue={setName} value={name}/>
<InputField title="Nickname (for others)" setValue={setNickname} value={nickname}/>
<InputField title="Password" setValue={setPassword} value={password}/>
<InputField title="Repeat Password" setValue={setRepeatPassword} value={repeatPassword}/>

160
src/pages/css/Chat.css Normal file
View File

@@ -0,0 +1,160 @@
body {
background: linear-gradient(to right, #e0f7fa, #fffde7);
}
.chat-container {
display: flex;
flex-direction: column;
width: 100%;
max-width: 600px;
margin: auto;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
background-color: #f9f9f9;
}
.chat-header {
padding: 15px;
background-color: #007bff;
color: white;
text-align: center;
font-weight: bold;
letter-spacing: 1px;
border-bottom: 2px solid #0056b3;
}
.chat-messages {
display: flex;
flex-direction: column;
flex-grow: 1;
padding: 10px;
overflow-y: auto;
background-color: #fff;
max-height: 400px;
}
.message-bubble {
margin: 5px;
padding: 10px 15px;
border-radius: 10px;
max-width: 70%;
word-wrap: break-word;
display: inline-block;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
transition: transform 0.2s;
}
.message-bubble:hover {
transform: translateY(-2px);
}
.sent {
background-color: #007bff;
color: white;
align-self: flex-end;
text-align: right;
margin-left: auto;
}
.received {
background-color: #f1f0f0;
align-self: flex-start;
text-align: left;
margin-right: auto;
}
.message-content {
font-size: 14px;
margin: 0;
padding: 0;
}
.message-timestamp {
font-size: 10px;
color: black;
text-align: right;
margin-top: 4px;
display: block;
}
.chat-input-container {
display: flex;
padding: 10px;
border-top: 1px solid #ddd;
background-color: #f0f0f0;
}
.chat-input {
flex-grow: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 20px;
font-size: 14px;
outline: none;
}
.send-button,
.emoji-button {
background-color: transparent;
border: none;
font-size: 20px;
margin-left: 10px;
cursor: pointer;
transition: color 0.3s;
}
.send-button:hover,
.emoji-button:hover {
color: #0056b3;
}
.send-button {
color: #007bff;
}
.emoji-button {
color: #f0c040;
}
.chat-input:focus {
border-color: #007bff;
}
.emoji-picker {
position: absolute;
bottom: 60px;
right: 10px;
z-index: 10;
background-color: white;
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
display: flex;
flex-wrap: wrap;
max-width: 200px;
}
.emoji {
font-size: 24px;
cursor: pointer;
transition: transform 0.2s;
}
.emoji:hover {
transform: scale(1.2);
}
.home-button {
background-color: #4caf50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
margin-left: auto;
}
.home-button:hover {
background-color: #45a049;
}

12
src/pages/css/account.css Normal file
View File

@@ -0,0 +1,12 @@
.account-items {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 5vw;
}
.account-items img {
margin-bottom: 1vw;
}

View File

@@ -1,3 +1,4 @@
@import url("css/init.css");
@import url("css/home.css");
@import url("css/input.css");
@import url("css/input.css");
@import url("css/account.css");

73
stubs/api/auth/index.js Normal file
View File

@@ -0,0 +1,73 @@
const authRouter = require('express').Router();
// For creating tokens
const jwt = require('jsonwebtoken');
const { TOKEN_KEY } = require('../key')
module.exports = authRouter;
const { addUserToDB, getUserFromDB } = require('../db');
// Get a user by its id
authRouter.get('/:id', (req, res) => {
const user = getUserFromDB(req.params.id);
if (user) {
res.status(200).send({user});
} else {
res.status(404).send({message: 'User was not found'});
}
})
// For login (authorization)
authRouter.post('/login', (req, res) => {
const { name, password } = req.body;
const user = getUserFromDB(name);
// Invalid identification
if (!user) {
res.status(401).send({message: 'Invalid credentials (id)'});
return;
}
// Invalid authentication
if (!password || password !== user.password) {
res.status(401).send({message: 'Invalid credentials (password)'});
return;
}
// Now, authorization
const token = jwt.sign({id: name}, TOKEN_KEY, {
expiresIn: '1h'
})
res.status(200).send({token});
})
authRouter.post('/reg', (req, res) => {
const { name, password, nickname } = req.body;
const user = getUserFromDB(name);
// Invalid identification
if (user) {
res.status(409).send({message: 'Such id already exists'});
return;
}
if (!name || !password || !nickname) {
res.status(401).send({message: 'Empty or invalid fields'});
return;
}
// Add to 'DB'
const newUser = {id: name, password: password, nickname: nickname};
addUserToDB(newUser)
res.status(200).send({user: newUser});
})

52
stubs/api/auth/users.json Normal file
View File

@@ -0,0 +1,52 @@
[
{
"nickname": "Alice Johnson",
"password": "1234",
"id": "alice"
},
{
"nickname": "Bob Smith",
"password": "1234",
"id": "bobsm"
},
{
"nickname": "Charlie Brown",
"password": "1234",
"id": "charl"
},
{
"nickname": "David Clark",
"password": "1234",
"id": "david"
},
{
"nickname": "Eve Adams",
"password": "1234",
"id": "evead"
},
{
"nickname": "Frank Wright",
"password": "1234",
"id": "frank"
},
{
"nickname": "Grace Lee",
"password": "1234",
"id": "grace"
},
{
"nickname": "Hannah Scott",
"password": "1234",
"id": "hanna"
},
{
"nickname": "Ian Davis",
"password": "1234",
"id": "ianda"
},
{
"nickname": "Jill Thompson",
"password": "1234",
"id": "jillt"
}
]

64
stubs/api/change/index.js Normal file
View File

@@ -0,0 +1,64 @@
const changeRouter = require('express').Router();
module.exports = changeRouter;
const { getUserFromDB, deleteUserFromDB, addUserToDB } = require('../db');
changeRouter.post('/nickname', (req, res) => {
const { id, newNickname } = req.body;
const user = getUserFromDB(id);
// Invalid identification
if (!user) {
res.status(401).send({message: 'Invalid credentials (id)'});
return;
}
const updatedUser = {
"nickname": newNickname,
"password": user.password,
"id": user.id
};
// Delete the old one
deleteUserFromDB(id)
// Insert updated
addUserToDB(updatedUser);
res.status(200).send({});
});
changeRouter.post('/password', (req, res) => {
const { id, newPassword } = req.body;
const user = getUserFromDB(id);
// Invalid identification
if (!user) {
res.status(401).send({message: 'Invalid credentials (id)'});
return;
}
// Delete the old one
deleteUserFromDB(id)
// Insert updated
const updatedUser = {
"nickname": user.nickname,
"password": newPassword,
"id": user.id
};
addUserToDB(updatedUser);
res.status(200).send({});
});
changeRouter.delete('/:id', (req, res) => {
const { id } = req.params;
deleteUserFromDB(id);
});

662
stubs/api/chat/chats.json Normal file
View File

@@ -0,0 +1,662 @@
[
{
"id1": "alice",
"id2": "bobsm",
"messages": [
{
"data": "Hello Bob!",
"senderId": "alice",
"recipientId": "bobsm",
"timestamp": "09.10.2024 07:00:00"
},
{
"data": "Hey Alice, how are you?",
"senderId": "bobsm",
"recipientId": "alice",
"timestamp": "09.10.2024 07:05:00"
},
{
"data": "I'm good, thanks for asking.",
"senderId": "alice",
"recipientId": "bobsm",
"timestamp": "09.10.2024 07:10:00"
},
{
"data": "Glad to hear!",
"senderId": "bobsm",
"recipientId": "alice",
"timestamp": "09.10.2024 07:15:00"
}
]
},
{
"id1": "alice",
"id2": "charl",
"messages": [
{
"data": "How's the project going?",
"senderId": "alice",
"recipientId": "charl",
"timestamp": "09.10.2024 07:20:00"
},
{
"data": "It's coming along, almost done!",
"senderId": "charl",
"recipientId": "alice",
"timestamp": "09.10.2024 07:25:00"
},
{
"data": "That's great to hear!",
"senderId": "alice",
"recipientId": "charl",
"timestamp": "09.10.2024 07:30:00"
},
{
"data": "Thanks for checking in.",
"senderId": "charl",
"recipientId": "alice",
"timestamp": "09.10.2024 07:35:00"
}
]
},
{
"id1": "alice",
"id2": "david",
"messages": [
{
"data": "Did you get the files?",
"senderId": "david",
"recipientId": "alice",
"timestamp": "09.10.2024 07:40:00"
},
{
"data": "Yes, I did. Thank you!",
"senderId": "alice",
"recipientId": "david",
"timestamp": "09.10.2024 07:45:00"
},
{
"data": "You're welcome.",
"senderId": "david",
"recipientId": "alice",
"timestamp": "09.10.2024 07:50:00"
},
{
"data": "Let me know if you need anything else.",
"senderId": "alice",
"recipientId": "david",
"timestamp": "09.10.2024 07:55:00"
}
]
},
{
"id1": "alice",
"id2": "frank",
"messages": [
{
"data": "Can you review this document for me?",
"senderId": "alice",
"recipientId": "frank",
"timestamp": "09.10.2024 08:20:00"
},
{
"data": "Sure, I'll take a look.",
"senderId": "frank",
"recipientId": "alice",
"timestamp": "09.10.2024 08:25:00"
},
{
"data": "Thanks, much appreciated!",
"senderId": "alice",
"recipientId": "frank",
"timestamp": "09.10.2024 08:30:00"
},
{
"data": "No problem.",
"senderId": "frank",
"recipientId": "alice",
"timestamp": "09.10.2024 08:35:00"
}
]
},
{
"id1": "alice",
"id2": "grace",
"messages": [
{
"data": "Hey Grace, let's meet up for coffee!",
"senderId": "alice",
"recipientId": "grace",
"timestamp": "09.10.2024 08:40:00"
},
{
"data": "Sounds good, when are you free?",
"senderId": "grace",
"recipientId": "alice",
"timestamp": "09.10.2024 08:45:00"
},
{
"data": "How about tomorrow afternoon?",
"senderId": "alice",
"recipientId": "grace",
"timestamp": "09.10.2024 08:50:00"
},
{
"data": "Works for me!",
"senderId": "grace",
"recipientId": "alice",
"timestamp": "09.10.2024 08:55:00"
}
]
},
{
"id1": "alice",
"id2": "hanna",
"messages": [
{
"data": "Hannah, do you have a moment?",
"senderId": "alice",
"recipientId": "hanna",
"timestamp": "09.10.2024 09:00:00"
},
{
"data": "Sure, what's up?",
"senderId": "hanna",
"recipientId": "alice",
"timestamp": "09.10.2024 09:05:00"
},
{
"data": "Just wanted to check on the report.",
"senderId": "alice",
"recipientId": "hanna",
"timestamp": "09.10.2024 09:10:00"
},
{
"data": "I'll send it soon.",
"senderId": "hanna",
"recipientId": "alice",
"timestamp": "09.10.2024 09:15:00"
}
]
},
{
"id1": "alice",
"id2": "ianda",
"messages": [
{
"data": "Ian, have you completed the review?",
"senderId": "alice",
"recipientId": "ianda",
"timestamp": "09.10.2024 09:20:00"
},
{
"data": "Yes, I sent my feedback.",
"senderId": "ianda",
"recipientId": "alice",
"timestamp": "09.10.2024 09:25:00"
},
{
"data": "Thanks for that.",
"senderId": "alice",
"recipientId": "ianda",
"timestamp": "09.10.2024 09:30:00"
},
{
"data": "Anytime!",
"senderId": "ianda",
"recipientId": "alice",
"timestamp": "09.10.2024 09:35:00"
}
]
},
{
"id1": "alice",
"id2": "jillt",
"messages": [
{
"data": "Jill, let's schedule a catch-up meeting.",
"senderId": "alice",
"recipientId": "jillt",
"timestamp": "09.10.2024 09:40:00"
},
{
"data": "Sounds good, when works for you?",
"senderId": "jillt",
"recipientId": "alice",
"timestamp": "09.10.2024 09:45:00"
},
{
"data": "Tomorrow afternoon?",
"senderId": "alice",
"recipientId": "jillt",
"timestamp": "09.10.2024 09:50:00"
},
{
"data": "That works for me!",
"senderId": "jillt",
"recipientId": "alice",
"timestamp": "09.10.2024 09:55:00"
}
]
},
{
"id1": "alice",
"id2": "evead",
"messages": [
{
"data": "Eve, did you send the schedule?",
"senderId": "alice",
"recipientId": "evead",
"timestamp": "09.10.2024 10:00:00"
},
{
"data": "Yes, just sent it.",
"senderId": "evead",
"recipientId": "alice",
"timestamp": "09.10.2024 10:05:00"
},
{
"data": "Thanks, much appreciated!",
"senderId": "alice",
"recipientId": "evead",
"timestamp": "09.10.2024 10:10:00"
},
{
"data": "No problem!",
"senderId": "evead",
"recipientId": "alice",
"timestamp": "09.10.2024 10:15:00"
}
]
},
{
"id1": "bobsm",
"id2": "charl",
"messages": [
{
"data": "How's everything going?",
"senderId": "bobsm",
"recipientId": "charl",
"timestamp": "09.10.2024 10:20:00"
},
{
"data": "Pretty good, how about you?",
"senderId": "charl",
"recipientId": "bobsm",
"timestamp": "09.10.2024 10:25:00"
},
{
"data": "Can't complain!",
"senderId": "bobsm",
"recipientId": "charl",
"timestamp": "09.10.2024 10:30:00"
},
{
"data": "Glad to hear that.",
"senderId": "charl",
"recipientId": "bobsm",
"timestamp": "09.10.2024 10:35:00"
}
]
},
{
"id1": "bobsm",
"id2": "david",
"messages": [
{
"data": "Can you send the report?",
"senderId": "bobsm",
"recipientId": "david",
"timestamp": "09.10.2024 10:40:00"
},
{
"data": "I'll send it in an hour.",
"senderId": "david",
"recipientId": "bobsm",
"timestamp": "09.10.2024 10:45:00"
},
{
"data": "Perfect, thanks.",
"senderId": "bobsm",
"recipientId": "david",
"timestamp": "09.10.2024 10:50:00"
},
{
"data": "No problem.",
"senderId": "david",
"recipientId": "bobsm",
"timestamp": "09.10.2024 10:55:00"
}
]
},
{
"id1": "charl",
"id2": "evead",
"messages": [
{
"data": "Hey Eve, how's it going?",
"senderId": "charl",
"recipientId": "evead",
"timestamp": "09.10.2024 11:00:00"
},
{
"data": "Good, how about you?",
"senderId": "evead",
"recipientId": "charl",
"timestamp": "09.10.2024 11:05:00"
},
{
"data": "Can't complain!",
"senderId": "charl",
"recipientId": "evead",
"timestamp": "09.10.2024 11:10:00"
},
{
"data": "Glad to hear.",
"senderId": "evead",
"recipientId": "charl",
"timestamp": "09.10.2024 11:15:00"
}
]
},
{
"id1": "charl",
"id2": "frank",
"messages": [
{
"data": "Do you have time to talk today?",
"senderId": "charl",
"recipientId": "frank",
"timestamp": "09.10.2024 11:20:00"
},
{
"data": "I have a meeting, but I can chat afterward.",
"senderId": "frank",
"recipientId": "charl",
"timestamp": "09.10.2024 11:25:00"
},
{
"data": "Sounds good.",
"senderId": "charl",
"recipientId": "frank",
"timestamp": "09.10.2024 11:30:00"
},
{
"data": "I'll message you after.",
"senderId": "frank",
"recipientId": "charl",
"timestamp": "09.10.2024 11:35:00"
}
]
},
{
"id1": "david",
"id2": "frank",
"messages": [
{
"data": "Did you review the document?",
"senderId": "david",
"recipientId": "frank",
"timestamp": "09.10.2024 11:40:00"
},
{
"data": "Yes, it's all good.",
"senderId": "frank",
"recipientId": "david",
"timestamp": "09.10.2024 11:45:00"
},
{
"data": "Great, thanks for the quick turnaround!",
"senderId": "david",
"recipientId": "frank",
"timestamp": "09.10.2024 11:50:00"
},
{
"data": "No worries!",
"senderId": "frank",
"recipientId": "david",
"timestamp": "09.10.2024 11:55:00"
}
]
},
{
"id1": "david",
"id2": "grace",
"messages": [
{
"data": "Grace, can you send the updated schedule?",
"senderId": "david",
"recipientId": "grace",
"timestamp": "09.10.2024 12:00:00"
},
{
"data": "Yes, I'll send it in a few minutes.",
"senderId": "grace",
"recipientId": "david",
"timestamp": "09.10.2024 12:05:00"
},
{
"data": "Thanks, much appreciated!",
"senderId": "david",
"recipientId": "grace",
"timestamp": "09.10.2024 12:10:00"
},
{
"data": "You're welcome!",
"senderId": "grace",
"recipientId": "david",
"timestamp": "09.10.2024 12:15:00"
}
]
},
{
"id1": "frank",
"id2": "grace",
"messages": [
{
"data": "How are you today?",
"senderId": "frank",
"recipientId": "grace",
"timestamp": "09.10.2024 12:20:00"
},
{
"data": "I'm doing well, thanks for asking.",
"senderId": "grace",
"recipientId": "frank",
"timestamp": "09.10.2024 12:25:00"
},
{
"data": "Glad to hear that.",
"senderId": "frank",
"recipientId": "grace",
"timestamp": "09.10.2024 12:30:00"
},
{
"data": "How about you?",
"senderId": "grace",
"recipientId": "frank",
"timestamp": "09.10.2024 12:35:00"
}
]
},
{
"id1": "frank",
"id2": "hanna",
"messages": [
{
"data": "Did you attend the meeting?",
"senderId": "frank",
"recipientId": "hanna",
"timestamp": "09.10.2024 12:40:00"
},
{
"data": "Yes, it was productive.",
"senderId": "hanna",
"recipientId": "frank",
"timestamp": "09.10.2024 12:45:00"
},
{
"data": "Good to hear!",
"senderId": "frank",
"recipientId": "hanna",
"timestamp": "09.10.2024 12:50:00"
},
{
"data": "Indeed, lots to follow up on.",
"senderId": "hanna",
"recipientId": "frank",
"timestamp": "09.10.2024 12:55:00"
}
]
},
{
"id1": "grace",
"id2": "hanna",
"messages": [
{
"data": "Can we meet later today?",
"senderId": "grace",
"recipientId": "hanna",
"timestamp": "09.10.2024 01:00:00"
},
{
"data": "Sure, what's a good time?",
"senderId": "hanna",
"recipientId": "grace",
"timestamp": "09.10.2024 01:05:00"
},
{
"data": "How about 3?",
"senderId": "grace",
"recipientId": "hanna",
"timestamp": "09.10.2024 01:10:00"
},
{
"data": "Works for me.",
"senderId": "hanna",
"recipientId": "grace",
"timestamp": "09.10.2024 01:15:00"
}
]
},
{
"id1": "grace",
"id2": "ianda",
"messages": [
{
"data": "Ian, did you get the message I sent?",
"senderId": "grace",
"recipientId": "ianda",
"timestamp": "09.10.2024 01:20:00"
},
{
"data": "Yes, I'll respond soon.",
"senderId": "ianda",
"recipientId": "grace",
"timestamp": "09.10.2024 01:25:00"
},
{
"data": "Thanks, appreciate it!",
"senderId": "grace",
"recipientId": "ianda",
"timestamp": "09.10.2024 01:30:00"
},
{
"data": "You're welcome!",
"senderId": "ianda",
"recipientId": "grace",
"timestamp": "09.10.2024 01:35:00"
}
]
},
{
"id1": "hanna",
"id2": "ianda",
"messages": [
{
"data": "Ian, do you have a minute?",
"senderId": "hanna",
"recipientId": "ianda",
"timestamp": "09.10.2024 01:40:00"
},
{
"data": "Yes, what do you need?",
"senderId": "ianda",
"recipientId": "hanna",
"timestamp": "09.10.2024 01:45:00"
},
{
"data": "Just a quick update on the project.",
"senderId": "hanna",
"recipientId": "ianda",
"timestamp": "09.10.2024 01:50:00"
},
{
"data": "I'll email you the details.",
"senderId": "ianda",
"recipientId": "hanna",
"timestamp": "09.10.2024 01:55:00"
}
]
},
{
"id1": "hanna",
"id2": "jillt",
"messages": [
{
"data": "Jill, can we talk tomorrow?",
"senderId": "hanna",
"recipientId": "jillt",
"timestamp": "09.10.2024 02:00:00"
},
{
"data": "Yes, I'm free after 2.",
"senderId": "jillt",
"recipientId": "hanna",
"timestamp": "09.10.2024 02:05:00"
},
{
"data": "Perfect, see you then.",
"senderId": "hanna",
"recipientId": "jillt",
"timestamp": "09.10.2024 02:10:00"
},
{
"data": "Looking forward to it.",
"senderId": "jillt",
"recipientId": "hanna",
"timestamp": "09.10.2024 02:15:00"
}
]
},
{
"id1": "ianda",
"id2": "jillt",
"messages": [
{
"data": "Jill, I have the files you requested.",
"senderId": "ianda",
"recipientId": "jillt",
"timestamp": "09.10.2024 02:20:00"
},
{
"data": "Thanks, please send them over.",
"senderId": "jillt",
"recipientId": "ianda",
"timestamp": "09.10.2024 02:25:00"
},
{
"data": "I'll send them right now.",
"senderId": "ianda",
"recipientId": "jillt",
"timestamp": "09.10.2024 02:30:00"
},
{
"data": "Great, thanks again!",
"senderId": "jillt",
"recipientId": "ianda",
"timestamp": "09.10.2024 02:35:00"
}
]
}
]

86
stubs/api/chat/index.js Normal file
View File

@@ -0,0 +1,86 @@
const chatRouter = require('express').Router();
module.exports = chatRouter;
const { getChatFromDB, getUsersChats, addChatToDB, getUserFromDB,
addMessageToChat} = require('../db');
chatRouter.get('/item/:id1/:id2', (req, res) => {
const { id1, id2 } = req.params;
if (id1 === id2) {
res.status(400).send({message: 'Ids should be different'});
return;
}
const chat = getChatFromDB(id1, id2);
if (chat) {
res.status(200).send({chat});
} else {
res.status(404).send({message: 'Chat was not found'});
}
})
chatRouter.post('/item/:id1/:id2', (req, res) => {
const { id1, id2 } = req.params;
if (id1 === id2) {
res.status(400).send({message: 'Ids should be different'});
return;
}
const chat = getChatFromDB(id1, id2);
if (chat) {
// Chat already exists
res.status(200).send({chat});
} else {
if (!getUserFromDB(id1) || !getUserFromDB(id2)) {
res.status(404).send({message: 'Such interlocutor does not exist'});
} else {
// Creating new chat
const newChat = {
id1: id1,
id2: id2,
messages: []
}
addChatToDB(newChat);
res.status(200).send({newChat});
}
}
})
chatRouter.get('/list/:id', (req, res) => {
const { id } = req.params;
const userChats = getUsersChats(id);
if (!userChats) {
res.status(404).send({message: 'Error with retrieving chats'});
} else {
res.status(200).send({chats: userChats});
}
})
chatRouter.post('/message/:sender/:receiver', (req, res) => {
const { sender, receiver } = req.params;
const { message } = req.body;
const chat = getChatFromDB(sender, receiver);
if (!chat) {
// Chat already exists
res.status(400).send({message: "Such chat does not exist"});
} else {
if (!getUserFromDB(sender) || !getUserFromDB(receiver)) {
res.status(404).send({message: 'Such people do not exist'});
} else {
// Add new message
addMessageToChat(chat, message);
res.status(200).send({});
}
}
})

74
stubs/api/db.js Normal file
View File

@@ -0,0 +1,74 @@
// Read already defined users (pseudo-DB)
const users = require('./auth/users.json');
const chats = require('./chat/chats.json');
const getUserFromDB = (userID) => {
if (!userID) {return false;}
// Accessing 'DB'
const user = users.find((user) => user.id === userID);
if (user) {
return user;
} else {
return false;
}
}
const deleteUserFromDB = (userID) => {
const index = users.findIndex(item => item.id === userID);
if (index !== -1) {
users.splice(index, 1);
}
}
const addUserToDB = (user) => {
users.push(user);
}
const getChatFromDB = (firstID, secondID) => {
if (!firstID || !secondID) {return false;}
// Accessing 'DB'
const chat = chats.find((item) =>
(item.id1 === firstID && item.id2 === secondID) || (item.id1 === secondID && item.id2 === firstID));
if (chat) {
return chat;
} else {
return false;
}
}
const getUsersChats = (userID) => {
if (!userID) {return false;}
const userChats = chats.filter((chat) => (chat.id1 === userID || chat.id2 === userID));
if (userChats) {
return userChats;
} else {
return false;
}
}
const addMessageToChat = (chat, msg) => {
chat.messages.push(msg);
}
const deleteChatFromDB = (firstID, secondID) => {
const index = chats.findIndex(item =>
(item.id1 === firstID && item.id2 === secondID) || (item.id1 === secondID && item.id2 === firstID));
if (index !== -1) {
chats.splice(index, 1);
}
}
const addChatToDB = (chat) => {
chats.push(chat);
}
module.exports = {users, chats, getUserFromDB, getChatFromDB, addUserToDB,
deleteUserFromDB, addChatToDB, deleteChatFromDB, getUsersChats, addMessageToChat}

View File

@@ -1,3 +1,17 @@
const changeRouter = require("./change");
const authRouter = require("./auth");
const chatRouter = require("./chat");
const router = require('express').Router();
const delay = require('./middlewares/delay');
const verify = require('./middlewares/verify');
module.exports = router;
// router.use(delay(300));
// router.use('/books', delay, booksRouter);
router.use('/auth', authRouter);
router.use('/change', verify, changeRouter);
router.use('/chat', verify, chatRouter)

3
stubs/api/key.js Normal file
View File

@@ -0,0 +1,3 @@
const TOKEN_KEY = '5frv12e4few3r';
module.exports = { TOKEN_KEY }

View File

@@ -0,0 +1,5 @@
const delay = (ms = 1000) => (req, res, next) => {
setTimeout(next, ms)
}
module.exports = delay

View File

@@ -0,0 +1,22 @@
const jwt = require('jsonwebtoken');
const { TOKEN_KEY } = require('../key')
function verifyToken(req, res, next) {
const token = req.headers['authorization']?.split(' ')[1];
if (!token) {
return res.status(401).send({ message: 'No token provided' });
}
// Verify token
jwt.verify(token, TOKEN_KEY, (err, decoded) => {
if (err) {
return res.status(401).send({ message: 'Unauthorized' });
}
next(); // Proceed to the next middleware or route
});
}
module.exports = verifyToken;