char implementation
This commit is contained in:
parent
4ac2740942
commit
61de5c81e7
1
.env
1
.env
|
@ -4,3 +4,4 @@ NEXT_PUBLIC_AGORA_APPID=ed90c9dc42634e5687d4e2e0766b363f
|
|||
NEXT_PUBLIC_CONTENTFUL_SPACE_ID = voxpxjq7y7vf
|
||||
NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN = s99GWKfpDKkNwiEJ3pN7US_tmqsGvDlaex-sOJwpzuc
|
||||
NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN = Z9WOKpLDbKNj7xVOmT_VXYNLH0AZwISFvQsq0PQlHfE
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"@ant-design/icons": "^5.2.6",
|
||||
"@ant-design/nextjs-registry": "^1.0.0",
|
||||
"@contentful/rich-text-react-renderer": "^15.22.9",
|
||||
"@microsoft/signalr": "^8.0.7",
|
||||
"agora-rtc-react": "^2.1.0",
|
||||
"agora-rtc-sdk-ng": "^4.20.2",
|
||||
"antd": "^5.12.1",
|
||||
|
@ -24,6 +25,7 @@
|
|||
"next-intl": "^3.3.1",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-signalr": "^0.2.24",
|
||||
"react-slick": "^0.29.0",
|
||||
"slick-carousel": "^1.8.1",
|
||||
"styled-components": "^6.1.1"
|
||||
|
@ -428,6 +430,19 @@
|
|||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/signalr": {
|
||||
"version": "8.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-8.0.7.tgz",
|
||||
"integrity": "sha512-PHcdMv8v5hJlBkRHAuKG5trGViQEkPYee36LnJQx4xHOQ5LL4X0nEWIxOp5cCtZ7tu+30quz5V3k0b1YNuc6lw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
"eventsource": "^2.0.2",
|
||||
"fetch-cookie": "^2.0.3",
|
||||
"node-fetch": "^2.6.7",
|
||||
"ws": "^7.4.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "14.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.3.tgz",
|
||||
|
@ -768,6 +783,13 @@
|
|||
"integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz",
|
||||
|
@ -974,6 +996,18 @@
|
|||
"resolved": "https://registry.npmjs.org/@vercel/stega/-/stega-0.1.2.tgz",
|
||||
"integrity": "sha512-P7mafQXjkrsoyTRppnt0N21udKS9wUmLXHRyP9saLXLHw32j/FgUJ3FscSWgvSqRs4cj7wKZtwqJEvWJ2jbGmA=="
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
|
@ -1821,7 +1855,6 @@
|
|||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
|
@ -1955,6 +1988,52 @@
|
|||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.6.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz",
|
||||
"integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"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",
|
||||
"peer": true,
|
||||
"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",
|
||||
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.17.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
|
||||
|
@ -2582,6 +2661,24 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/event-target-shim": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/eventsource": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
|
||||
"integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-copy": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-2.1.7.tgz",
|
||||
|
@ -2664,6 +2761,16 @@
|
|||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-cookie": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz",
|
||||
"integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"set-cookie-parser": "^2.4.8",
|
||||
"tough-cookie": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
||||
|
@ -3137,6 +3244,12 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hermes-channel": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/hermes-channel/-/hermes-channel-2.1.2.tgz",
|
||||
"integrity": "sha512-PVH+X8/S9J6XItQgIRLlsrwXUmb/v13wxvcZgqohnnlUZZOEWbWZ07bLsuQ9dEMnNpT9APvBuVV50W5QmDt4pA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
|
@ -3654,6 +3767,12 @@
|
|||
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/js-cookie": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz",
|
||||
"integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
|
@ -3909,8 +4028,7 @@
|
|||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
|
@ -4053,6 +4171,26 @@
|
|||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||
|
@ -4434,11 +4572,16 @@
|
|||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
|
||||
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
@ -4457,6 +4600,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/querystringify": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
@ -5096,6 +5245,22 @@
|
|||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/react-signalr": {
|
||||
"version": "0.2.24",
|
||||
"resolved": "https://registry.npmjs.org/react-signalr/-/react-signalr-0.2.24.tgz",
|
||||
"integrity": "sha512-d7Fk2ZibAxUi3t4ovBuGaTSSWzsTLpW8Wh9HCgzPBomkufnykUhe2vS1uBLfbTDlUaaEjoLlQQjk+ZXJoilmhg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hermes-channel": "^2.1.2",
|
||||
"js-cookie": "^2.2.1",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@microsoft/signalr": "^8.0.0",
|
||||
"react": ">=16.13",
|
||||
"socket.io-client": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-slick": {
|
||||
"version": "0.29.0",
|
||||
"resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.29.0.tgz",
|
||||
|
@ -5168,6 +5333,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resize-observer-polyfill": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||
|
@ -5363,6 +5534,12 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
|
@ -5466,6 +5643,36 @@
|
|||
"jquery": ">=1.8.0"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"peer": true,
|
||||
"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",
|
||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.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",
|
||||
|
@ -5865,6 +6072,27 @@
|
|||
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
|
||||
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.2.0",
|
||||
"url-parse": "^1.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
|
||||
|
@ -6046,6 +6274,15 @@
|
|||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
|
||||
|
@ -6085,6 +6322,16 @@
|
|||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-intl": {
|
||||
"version": "3.17.4",
|
||||
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-3.17.4.tgz",
|
||||
|
@ -6097,6 +6344,15 @@
|
|||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
|
||||
|
@ -6117,6 +6373,12 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/webrtc-adapter": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.0.tgz",
|
||||
|
@ -6129,6 +6391,16 @@
|
|||
"npm": ">=3.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
@ -6332,6 +6604,36 @@
|
|||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "7.5.10",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
||||
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"@ant-design/icons": "^5.2.6",
|
||||
"@ant-design/nextjs-registry": "^1.0.0",
|
||||
"@contentful/rich-text-react-renderer": "^15.22.9",
|
||||
"@microsoft/signalr": "^8.0.7",
|
||||
"agora-rtc-react": "^2.1.0",
|
||||
"agora-rtc-sdk-ng": "^4.20.2",
|
||||
"antd": "^5.12.1",
|
||||
|
@ -25,6 +26,7 @@
|
|||
"next-intl": "^3.3.1",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-signalr": "^0.2.24",
|
||||
"react-slick": "^0.29.0",
|
||||
"slick-carousel": "^1.8.1",
|
||||
"styled-components": "^6.1.1"
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import {apiRequest} from "../helpers";
|
||||
|
||||
|
||||
export const getChatList = (locale: string, token: string): Promise<any> => apiRequest({
|
||||
url: '/chat/chatList',
|
||||
method: 'get',
|
||||
locale,
|
||||
token
|
||||
});
|
||||
|
||||
export const getChatMessages = (locale: string, token: string, group: number): Promise<any> => apiRequest({
|
||||
url: '/chat/chat_messages/'+group,
|
||||
method: 'get',
|
||||
locale,
|
||||
token
|
||||
});
|
|
@ -1,8 +1,9 @@
|
|||
'use client'
|
||||
import React from 'react';
|
||||
import { unstable_setRequestLocale } from 'next-intl/server';
|
||||
import { Link } from '../../../../../../navigation';
|
||||
import { i18nText } from '../../../../../../i18nKeys';
|
||||
|
||||
import {ChatMessages} from "../../../../../../components/Chat/ChatMessages";
|
||||
/*
|
||||
export function generateStaticParams({
|
||||
params: { locale },
|
||||
}: { params: { locale: string } }) {
|
||||
|
@ -15,56 +16,22 @@ export function generateStaticParams({
|
|||
|
||||
return result;
|
||||
}
|
||||
*
|
||||
*/
|
||||
|
||||
export default function Message({ params }: { params: { locale: string, textId: string } }) {
|
||||
unstable_setRequestLocale(params.locale);
|
||||
export default function Message({ params: { locale, textId } }: { params: { locale: string, textId: string } }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link href={'/account/messages' as any}>
|
||||
{i18nText('accountMenu.messages', params.locale)}
|
||||
{i18nText('accountMenu.messages', locale)}
|
||||
</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item active" aria-current="page">{`Person ${params.textId}`}</li>
|
||||
</ol>
|
||||
|
||||
<div className="b-message">
|
||||
<div className="b-message__inner">
|
||||
<div className="b-message__list b-message__list--me">
|
||||
<div className="b-message__item ">
|
||||
<div className="b-message__avatar">
|
||||
<img src="/images/person.png" className="" alt="" />
|
||||
</div>
|
||||
<div className="b-message__text">
|
||||
🤩 It all for you!
|
||||
|
||||
<span className="date">07.09.2022</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="b-message__list">
|
||||
<div className="b-message__item">
|
||||
<div className="b-message__avatar">
|
||||
<img src="/images/person.png" className="" alt="" />
|
||||
</div>
|
||||
<div className="b-message__text">
|
||||
🤩 It all for you!
|
||||
<span className="date">07.09.2022</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form className="b-message__form" action="">
|
||||
<textarea placeholder="Type your message here" name="" id="" />
|
||||
<label className="b-message__upload-file">
|
||||
<input type="file" required />
|
||||
</label>
|
||||
<div className="b-message__microphone" />
|
||||
<button className="b-message__btn" type="submit" />
|
||||
</form>
|
||||
</div>
|
||||
<ChatMessages locale={locale} groupId={parseInt(textId)} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
'use client'
|
||||
import React, { Suspense } from 'react';
|
||||
import { unstable_setRequestLocale } from 'next-intl/server';
|
||||
import { Link } from '../../../../../navigation';
|
||||
import { CustomInput } from '../../../../../components/view/CustomInput';
|
||||
import { i18nText } from '../../../../../i18nKeys';
|
||||
import {ChatList} from "../../../../../components/Chat/ChatList";
|
||||
|
||||
export default function Messages({ params: { locale } }: { params: { locale: string } }) {
|
||||
unstable_setRequestLocale(locale);
|
||||
|
||||
export default function Messages({ params: { locale } }: { params: { locale: string } }) {
|
||||
return (
|
||||
<>
|
||||
<ol className="breadcrumb">
|
||||
|
@ -15,74 +13,7 @@ export default function Messages({ params: { locale } }: { params: { locale: str
|
|||
<Suspense>
|
||||
<CustomInput placeholder={i18nText('name', locale)} />
|
||||
</Suspense>
|
||||
<div className="messages-session">
|
||||
<Link
|
||||
className="card-profile"
|
||||
href={'1' as any}
|
||||
>
|
||||
<div className="card-profile__header">
|
||||
<div className="card-profile__header__portrait">
|
||||
<img src="/images/person.png" className="" alt="" />
|
||||
</div>
|
||||
<div className="card-profile__header__inner">
|
||||
<div style={{ width: '100%' }}>
|
||||
<div className="card-profile__header__name">
|
||||
David
|
||||
<span className="count">14</span>
|
||||
</div>
|
||||
<div className="card-profile__header__title">
|
||||
Lorem ipsum dolor sit at, consecte...
|
||||
</div>
|
||||
<div className="card-profile__header__date ">
|
||||
25 may
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
className="card-profile"
|
||||
href={'2' as any}
|
||||
>
|
||||
<div className="card-profile__header">
|
||||
<div className="card-profile__header__portrait">
|
||||
<img src="/images/person.png" className="" alt="" />
|
||||
</div>
|
||||
<div className="card-profile__header__inner">
|
||||
<div style={{ width: '100%' }}>
|
||||
<div className="card-profile__header__name">David</div>
|
||||
<div className="card-profile__header__title">
|
||||
Lorem ipsum dolor sit at, consecte...
|
||||
</div>
|
||||
<div className="card-profile__header__date ">
|
||||
25 may
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
className="card-profile"
|
||||
href={'3' as any}
|
||||
>
|
||||
<div className="card-profile__header">
|
||||
<div className="card-profile__header__portrait">
|
||||
<img src="/images/person.png" className="" alt="" />
|
||||
</div>
|
||||
<div className="card-profile__header__inner">
|
||||
<div style={{ width: '100%' }}>
|
||||
<div className="card-profile__header__name">David</div>
|
||||
<div className="card-profile__header__title">
|
||||
Lorem ipsum dolor sit at, consecte...
|
||||
</div>
|
||||
<div className="card-profile__header__date ">
|
||||
25 may
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<ChatList locale={locale}/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,21 +4,44 @@ import { Button } from 'antd';
|
|||
import { useSelectedLayoutSegment, usePathname } from 'next/navigation';
|
||||
import { Link } from '../../navigation';
|
||||
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../constants/common';
|
||||
import { deleteStorageKey } from '../../hooks/useLocalStorage';
|
||||
import {deleteStorageKey, useLocalStorage} from '../../hooks/useLocalStorage';
|
||||
import { i18nText } from '../../i18nKeys';
|
||||
import { getMenuConfig } from '../../utils/account';
|
||||
import {useEffect, useState} from "react";
|
||||
import {getChatList} from "../../actions/chat/groups";
|
||||
|
||||
export const AccountMenu = ({ locale }: { locale: string }) => {
|
||||
const selectedLayoutSegment = useSelectedLayoutSegment();
|
||||
const pathname = selectedLayoutSegment || '';
|
||||
const paths = usePathname();
|
||||
const menu: { path: string, title: string, count?: number }[] = getMenuConfig(locale);
|
||||
const [counts, setCounts] = useState<any>({});
|
||||
|
||||
const onLogout = () => {
|
||||
deleteStorageKey(AUTH_TOKEN_KEY);
|
||||
deleteStorageKey(AUTH_USER);
|
||||
window?.location?.replace(`/${paths.split('/')[1]}/`);
|
||||
};
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
let init = false
|
||||
useEffect(() => {
|
||||
if (jwt && locale && !init) {
|
||||
init = true;
|
||||
getChatList(locale, jwt).then((payload: any)=> {
|
||||
if (payload?.directs) {
|
||||
let summ = 0;
|
||||
payload?.directs.forEach((el: any) => {
|
||||
summ = summ + el.newMessagesCount
|
||||
})
|
||||
setCounts({'messages': summ})
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [jwt, locale])
|
||||
|
||||
const getterCount = (path: string, count: number)=> {
|
||||
return counts[path]? counts[path] : count
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="list-sidebar">
|
||||
|
@ -26,8 +49,8 @@ export const AccountMenu = ({ locale }: { locale: string }) => {
|
|||
<li key={path} className="list-sidebar__item">
|
||||
<Link href={`/account/${path}` as any} className={path === pathname ? 'active' : ''}>
|
||||
{title}
|
||||
{count ? (
|
||||
<span className="count">{count}</span>
|
||||
{getterCount(path, count) ? (
|
||||
<span className="count">{getterCount(path, count)}</span>
|
||||
) : null}
|
||||
</Link>
|
||||
</li>
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
'use client'
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {AUTH_TOKEN_KEY} from '../../constants/common';
|
||||
import {getChatList, getChatMessages} from "../../actions/chat/groups";
|
||||
import {useLocalStorage} from "../../hooks/useLocalStorage";
|
||||
import {Link} from "../../navigation";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import {message} from "antd";
|
||||
import {Loader} from "../view/Loader";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
type CompProps = {
|
||||
locale: string;
|
||||
};
|
||||
|
||||
export const ChatList = ({ locale }: CompProps) => {
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
const [chats, setСhats] = useState<any | undefined>();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
useEffect(() => {
|
||||
if (jwt) {
|
||||
setLoading(true);
|
||||
Promise.all([
|
||||
getChatList(locale, jwt),
|
||||
])
|
||||
.then(([_groups]) => {
|
||||
setСhats(_groups)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
message.error('Не удалось загрузить данные');
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
})
|
||||
}
|
||||
},[jwt])
|
||||
|
||||
return (
|
||||
<div className="messages-session">
|
||||
<Loader isLoading={loading}>
|
||||
{chats?.directs.map((item: any, i: number) => (
|
||||
<Link
|
||||
key={'chat'+i}
|
||||
className="card-profile"
|
||||
href={'messages/'+item.group.id as any}
|
||||
>
|
||||
<div className="card-profile__header">
|
||||
<div className="card-profile__header__portrait">
|
||||
<img src={item.faceImageUrl} className="" alt="" />
|
||||
</div>
|
||||
<div className="card-profile__header__inner">
|
||||
<div style={{ width: '100%' }}>
|
||||
<div className="card-profile__header__name">
|
||||
{item.firstName}
|
||||
{item.newMessagesCount && (
|
||||
<span className="count">{item.newMessagesCount}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="card-profile__header__title">
|
||||
{item?.lastMessage?.text}
|
||||
</div>
|
||||
<div className="card-profile__header__date ">
|
||||
{dayjs(item?.lastMessage?.sentAt).fromNow()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</Loader>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
'use client'
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {AUTH_TOKEN_KEY} from '../../constants/common';
|
||||
import {getChatList, getChatMessages} from "../../actions/chat/groups";
|
||||
import {useLocalStorage} from "../../hooks/useLocalStorage";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
|
||||
import {message} from "antd";
|
||||
import {Loader} from "../view/Loader";
|
||||
import SignalrConnection from "../../lib/signalr-connection";
|
||||
import {CheckOutlined} from "@ant-design/icons";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
type CompProps = {
|
||||
locale: string;
|
||||
groupId: number
|
||||
};
|
||||
|
||||
export const ChatMessages = ({ locale, groupId }: CompProps) => {
|
||||
const { newMessage, joinChat, readMessages, addListener } = SignalrConnection();
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
//const messages = await getChatMessages(locale, jwt, groupId)
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [text, setText] = useState('');
|
||||
const [me, setMe] = useState<any | undefined>();
|
||||
const [notMe, setNotMe] = useState<any | undefined>();
|
||||
const [messages, setMessages] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (jwt) {
|
||||
setLoading(true);
|
||||
Promise.all([
|
||||
getChatList(locale, jwt),
|
||||
getChatMessages(locale, jwt, groupId),
|
||||
])
|
||||
.then(([_groups, _messages]) => {
|
||||
//
|
||||
const _group = _groups.directs.find( (el: any) => el.group.id === groupId)
|
||||
if (_group.group.members[0].userId === parseInt(_group.userId)){
|
||||
setMe(_group.group.members[0].user)
|
||||
setNotMe(_group.group.members[1].user)
|
||||
} else {
|
||||
setMe(_group.group.members[1].user)
|
||||
setNotMe(_group.group.members[0].user)
|
||||
}
|
||||
setMessages(_messages.messages);
|
||||
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
message.error('Не удалось загрузить данные');
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
})
|
||||
}
|
||||
},[jwt])
|
||||
|
||||
|
||||
const onConnected = (flag: boolean) =>{
|
||||
if (flag) {
|
||||
joinChat(groupId)
|
||||
readUreaded()
|
||||
}
|
||||
}
|
||||
|
||||
const readUreaded = () => {
|
||||
const msgs = [] as number[];
|
||||
messages.forEach((message: any) => {
|
||||
if (!message.seen && message.sentByMe === false){
|
||||
msgs.push(message.id)
|
||||
}
|
||||
})
|
||||
readMessages(msgs)
|
||||
}
|
||||
|
||||
const onReceiveMessage = (payload: any) => {
|
||||
const _messages = [... messages];
|
||||
let flag = false;
|
||||
const msg = {
|
||||
data : payload.data,
|
||||
dataType: null,
|
||||
id: payload.id,
|
||||
receiverId:null,
|
||||
seen: false,
|
||||
senderId: payload.creatorId,
|
||||
sentAt: payload.createdUtc+'.000Z',
|
||||
sentByMe: payload.creatorId === me.id,
|
||||
text: payload.content,
|
||||
type:"text"
|
||||
}
|
||||
_messages.forEach((item: any, i) => {
|
||||
if (item.id === msg.id){
|
||||
_messages[i] = msg
|
||||
flag = true
|
||||
}
|
||||
})
|
||||
//console.log(payload, flag)
|
||||
if (flag){
|
||||
setMessages([..._messages]);
|
||||
} else {
|
||||
setMessages([msg, ..._messages]);
|
||||
}
|
||||
readMessages([msg.id])
|
||||
|
||||
}
|
||||
|
||||
const onOpponentRead = (payload: any) => {
|
||||
console.log('onOpponentRead', payload)
|
||||
const _messages = [... messages] as any[];
|
||||
_messages.forEach((item: any, i) => {
|
||||
if (item.id === payload.messageId){
|
||||
_messages[i].seen = true
|
||||
|
||||
}
|
||||
})
|
||||
setMessages([..._messages])
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
addListener('onConnected', onConnected)
|
||||
addListener('ReceiveMessage', onReceiveMessage)
|
||||
addListener('MessageWasRead', onOpponentRead)
|
||||
}, [messages, me, setMessages]);
|
||||
|
||||
|
||||
|
||||
|
||||
const handleSendMessages = () => {
|
||||
newMessage({
|
||||
GroupId: groupId.toString(),
|
||||
CreatorId: me.id,
|
||||
Content: text
|
||||
})
|
||||
setText('')
|
||||
}
|
||||
|
||||
const onEnterPress = (e: any) => {
|
||||
if(e.keyCode == 13 && e.shiftKey == false) {
|
||||
e.preventDefault();
|
||||
handleSendMessages();
|
||||
}
|
||||
}
|
||||
|
||||
const handleChangeMessage = (e: any) =>{
|
||||
const { value } = e.target;
|
||||
e.preventDefault();
|
||||
setText(value);
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
const messageRender = (message: any, i:number) => {
|
||||
const item = message.sentByMe ? me : notMe
|
||||
const date = dayjs(message.sentAt).fromNow()
|
||||
const imgSrc = item?.faceImage?.descriptor ? 'http://static.bbuddy.expert/' + item.faceImage.descriptor.split(':')[1] : ''
|
||||
return (
|
||||
<div
|
||||
className={message.sentByMe ? 'b-message__list b-message__list--me' : 'b-message__list'}
|
||||
key={'message'+i}
|
||||
>
|
||||
<div className="b-message__item ">
|
||||
<div className="b-message__avatar">
|
||||
{imgSrc && (<img src={imgSrc} className="" alt=""/>)}
|
||||
</div>
|
||||
<div className="b-message__text" style={{minWidth: '150px'}}>
|
||||
{message.text}
|
||||
<span className="date">
|
||||
<span className="checks" style={{color: message.seen ? 'green' : 'blue'}}>
|
||||
<CheckOutlined />
|
||||
{ message?.seen && (<CheckOutlined style={{marginLeft: '-10px'}} />)}
|
||||
</span>
|
||||
{date}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<div className="b-message">
|
||||
<Loader isLoading={loading}>
|
||||
<div className="b-message__inner">
|
||||
{messages.map((el, i)=> (messageRender(el, i)))}
|
||||
</div>
|
||||
</Loader>
|
||||
<div className="b-message__form">
|
||||
<textarea placeholder="Type your message here" onKeyDown={onEnterPress}
|
||||
onChange={handleChangeMessage} value={text}/>
|
||||
<button className="b-message__btn" type="submit" onClick={handleSendMessages}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import React, {FC, useEffect, useState} from 'react';
|
||||
import Image from 'next/image';
|
||||
import { Tag, Image as AntdImage, Space } from 'antd';
|
||||
import { ZoomInOutlined, ZoomOutOutlined, StarFilled } from '@ant-design/icons';
|
||||
|
@ -10,6 +10,10 @@ import { Locale } from '../../types/locale';
|
|||
import { CustomRate } from '../view/CustomRate';
|
||||
import { i18nText } from '../../i18nKeys';
|
||||
import { FilledYellowButton } from '../view/FilledButton';
|
||||
import SignalrConnection from "../../lib/signalr-connection";
|
||||
import {useRouter} from "../../navigation";
|
||||
import {useLocalStorage} from "../../hooks/useLocalStorage";
|
||||
import {AUTH_TOKEN_KEY} from "../../constants/common";
|
||||
|
||||
type ExpertDetailsProps = {
|
||||
expert: ExpertDetails;
|
||||
|
@ -61,8 +65,35 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale }) => {
|
|||
};
|
||||
|
||||
export const ExpertInformation: FC<ExpertDetailsProps> = ({ expert, locale }) => {
|
||||
const { publicCoachDetails: { tags = [], sessionCost = 0, sessionDuration = 0, coachLanguages = [] } } = expert || {};
|
||||
const { publicCoachDetails: { tags = [], sessionCost = 0, sessionDuration = 0, coachLanguages = [] , id, botUserId} } = expert || {};
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
const isRus = locale === Locale.ru;
|
||||
const { joinChatPerson, addListener } = SignalrConnection();
|
||||
const router = useRouter();
|
||||
|
||||
const onConnected = (flag: boolean)=>{
|
||||
if (flag){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
addListener('onConnected', onConnected)
|
||||
}, []);
|
||||
|
||||
const handleJoinChat = () =>{
|
||||
joinChatPerson(id).then((res: any) =>{
|
||||
console.log('RES', res)
|
||||
router.push(`/account/messages/${res.id}`);
|
||||
})
|
||||
}
|
||||
|
||||
const handleJoinAIChat = () =>{
|
||||
joinChatPerson(botUserId).then((res: any) =>{
|
||||
console.log('RES', res)
|
||||
router.push(`/account/messages/${res.id}`);
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -95,16 +126,29 @@ export const ExpertInformation: FC<ExpertDetailsProps> = ({ expert, locale }) =>
|
|||
<div className="wrap-btn-prise__text">
|
||||
{`${sessionCost}€`} <span>/ {`${sessionDuration}${isRus ? 'мин' : 'min'}`}</span>
|
||||
</div>
|
||||
{jwt && (
|
||||
<div className="w-100" style={{textAlign: 'end'}}>
|
||||
|
||||
<FilledYellowButton
|
||||
onClick={handleJoinChat}
|
||||
>{i18nText('chat.join', locale)}</FilledYellowButton>
|
||||
|
||||
{botUserId && (
|
||||
<FilledYellowButton
|
||||
onClick={handleJoinAIChat}>{i18nText('chat.joinAI', locale)}</FilledYellowButton>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ExpertPractice: FC<ExpertPracticeProps> = ({ themes = [], cases = [], locale }) => {
|
||||
export const ExpertPractice: FC<ExpertPracticeProps> = ({themes = [], cases = [], locale}) => {
|
||||
return cases?.length > 0 ? (
|
||||
<div>
|
||||
<h3 className="title-h3">{i18nText('successfulCase', locale)}</h3>
|
||||
{cases?.map(({ id, description, themesGroupIds }) => {
|
||||
{cases?.map(({id, description, themesGroupIds}) => {
|
||||
const filtered = themes ? themes.filter(({ id }) => themesGroupIds?.includes(+id)) : [];
|
||||
|
||||
return (
|
||||
|
|
|
@ -146,6 +146,10 @@ export default {
|
|||
saturday: 'Сб',
|
||||
addNew: 'Добавить',
|
||||
mExperiences: 'Управленческий опыт',
|
||||
chat: {
|
||||
join: 'начать чат',
|
||||
joinAI: 'начать чат с ИИ'
|
||||
},
|
||||
errors: {
|
||||
invalidEmail: 'Адрес электронной почты недействителен',
|
||||
emptyEmail: 'Пожалуйста, введите ваш E-mail',
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
import { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
|
||||
import {AUTH_TOKEN_KEY} from "../constants/common";
|
||||
import {IHttpConnectionOptions} from "@microsoft/signalr/src/IHttpConnectionOptions";
|
||||
import {IChatMessage} from "../types/chat";
|
||||
|
||||
const URL = 'https://api.bbuddy.expert/api/hubs/chat'
|
||||
|
||||
const chatMessageMethodName = 'ReceiveMessage';
|
||||
const chatUserStatusChangeMethodName = 'chatUserStatusChange';
|
||||
const chatSeenMethodName = 'MessageWasRead';
|
||||
const chatTypingMethodName = 'directTyping';
|
||||
const sendChatTextMethodName = 'SendMessage';
|
||||
const sendChatTypingMethodName = 'DirectIsTyping';
|
||||
const serverError = 'Error';
|
||||
|
||||
const sendChatSeenMethodName = 'ConfirmMessageRead';
|
||||
const sendChatMessagesSeenMethodName = 'ConfirmMessagesRead';
|
||||
|
||||
|
||||
const joinChatMethodName = 'JoinChat';
|
||||
const leaveChatMethodName = 'LeaveChat';
|
||||
|
||||
const joinChatsMethodName = 'JoinChats';
|
||||
const leaveChatsMethodName = 'LeaveChats';
|
||||
const chatsReceiveMessageMethodName = 'ChatsMessageCreated';
|
||||
|
||||
class SignalConnector {
|
||||
private connection: HubConnection;
|
||||
private events ={} as any;
|
||||
static instance: SignalConnector;
|
||||
constructor() {
|
||||
const options = {
|
||||
accessTokenFactory: () => localStorage.getItem(AUTH_TOKEN_KEY)
|
||||
} as IHttpConnectionOptions;
|
||||
this.connection = new HubConnectionBuilder()
|
||||
.withUrl(URL, options)
|
||||
.withAutomaticReconnect()
|
||||
.configureLogging(LogLevel.Debug)
|
||||
.build();
|
||||
this.connection.start().then(()=>{
|
||||
this.events?.onConnected(true)
|
||||
for (const k in this.events) {
|
||||
if (k != 'onConnected'){
|
||||
this.connection.on(k, (payload: any) => {
|
||||
this.events[k](payload);
|
||||
});
|
||||
}
|
||||
}
|
||||
}).catch(err => console.log('SignalR Error', err));
|
||||
}
|
||||
public addListener = (name: string, func: any)=> {
|
||||
this.events[name] = func;
|
||||
}
|
||||
|
||||
public closeConnection = () =>{
|
||||
this.connection.stop();
|
||||
}
|
||||
|
||||
public newMessage = (message: IChatMessage) => {
|
||||
this.connection.invoke(sendChatTextMethodName, message).then(x => console.log('NewMsg',x))
|
||||
}
|
||||
public joinChat = (groupId: number) => {
|
||||
this.connection.invoke(joinChatMethodName, groupId, null).then(x => console.log(joinChatMethodName, x))
|
||||
}
|
||||
|
||||
public joinChatPerson = (accId: number) => {
|
||||
return this.connection.invoke(joinChatMethodName, 0, accId)
|
||||
}
|
||||
|
||||
public readMessages = (messagesId: number[]) => {
|
||||
this.connection.invoke(sendChatMessagesSeenMethodName, messagesId).then(x => console.log(sendChatMessagesSeenMethodName, x))
|
||||
}
|
||||
|
||||
public static getInstance(): SignalConnector {
|
||||
if (!SignalConnector.instance)
|
||||
SignalConnector.instance = new SignalConnector();
|
||||
return SignalConnector.instance;
|
||||
}
|
||||
}
|
||||
|
||||
export default SignalConnector.getInstance;
|
|
@ -13,6 +13,8 @@
|
|||
}
|
||||
|
||||
&__inner {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
flex-grow: 1;
|
||||
height: 0;
|
||||
position: relative;
|
||||
|
@ -94,7 +96,8 @@
|
|||
align-items: center;
|
||||
|
||||
textarea {
|
||||
width: calc(100% - 136px);
|
||||
resize: none;
|
||||
width: calc(100% - 40px);
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
border-radius: 0;
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
export interface IChatMessage {
|
||||
GroupId: string;
|
||||
CreatorId: number;
|
||||
Content: string;
|
||||
}
|
||||
|
||||
export interface IChatJoin {
|
||||
GroupId: string;
|
||||
CreatorId: number;
|
||||
Content: string;
|
||||
}
|
|
@ -37,6 +37,7 @@ export type ThemeGroup = {
|
|||
|
||||
export interface ExpertItem {
|
||||
id: number;
|
||||
botUserId?: number;
|
||||
name: string;
|
||||
surname?: string;
|
||||
faceImageUrl?: string;
|
||||
|
|
|
@ -5,8 +5,7 @@ import { i18nText } from '../i18nKeys';
|
|||
const ROUTES = ['sessions', 'notifications', 'support', 'information', 'settings', 'messages', 'expert-profile'];
|
||||
const COUNTS: Record<string, number> = {
|
||||
sessions: 12,
|
||||
notifications: 5,
|
||||
messages: 113
|
||||
notifications: 5
|
||||
};
|
||||
|
||||
export const getMenuConfig = (locale: string) => ROUTES.map((path) => ({
|
||||
|
|
Loading…
Reference in New Issue