From 46c040c64229da3aaa44102dd1bc881911197fb5 Mon Sep 17 00:00:00 2001 From: StepanovPlaton Date: Mon, 17 Mar 2025 10:01:32 +0400 Subject: [PATCH] Complete project --- backend/.env.example | 1 + backend/package-lock.json | 378 +++++- backend/package.json | 7 +- .../controllers/message/message.controller.ts | 10 +- backend/src/entities/message/message.ts | 3 + backend/src/main.ts | 5 +- backend/src/modules/app/app.module.ts | 5 +- backend/src/modules/message/message.module.ts | 3 +- backend/src/shared/gateways/message/index.ts | 3 + .../gateways/message/message.gateway.ts | 17 + docker-compose-all.yml | 50 + frontend/.env | 1 - frontend/.env.development | 16 +- frontend/next.config.ts | 11 +- frontend/package-lock.json | 1022 ++++++++++++++++- frontend/package.json | 8 +- frontend/postcss.config.mjs | 4 +- frontend/src/app/globals.css | 46 +- frontend/src/app/layout.tsx | 4 +- frontend/src/app/page.tsx | 7 +- frontend/src/entities/message/index.ts | 5 + .../src/entities/message/message.schema.ts | 11 + .../src/entities/message/message.service.ts | 16 + frontend/src/entities/user/index.ts | 5 + frontend/src/entities/user/user.schema.ts | 8 + frontend/src/entities/user/user.service.ts | 25 + .../colorSchemeSwitch/colorSchemeSwitch.tsx | 17 + .../src/features/colorSchemeSwitch/index.ts | 3 + frontend/src/features/messagesList/index.ts | 3 + .../src/features/messagesList/message.tsx | 28 + .../features/messagesList/messagesList.tsx | 73 ++ frontend/src/features/sendMessage/index.ts | 3 + .../src/features/sendMessage/sendMessage.tsx | 41 + frontend/src/shared/assets/icons/index.ts | 2 + frontend/src/shared/assets/icons/sendIcon.tsx | 33 + frontend/src/shared/assets/icons/sunIcon.tsx | 31 + frontend/src/shared/services/http/http.ts | 95 ++ frontend/src/shared/services/http/index.ts | 3 + frontend/src/widgets/chat-window/index.ts | 3 + frontend/src/widgets/chat-window/window.tsx | 25 + frontend/tailwind.config.ts | 47 - 41 files changed, 1951 insertions(+), 127 deletions(-) create mode 100644 backend/src/shared/gateways/message/index.ts create mode 100644 backend/src/shared/gateways/message/message.gateway.ts create mode 100644 docker-compose-all.yml create mode 100644 frontend/src/entities/message/index.ts create mode 100644 frontend/src/entities/message/message.schema.ts create mode 100644 frontend/src/entities/message/message.service.ts create mode 100644 frontend/src/entities/user/index.ts create mode 100644 frontend/src/entities/user/user.schema.ts create mode 100644 frontend/src/entities/user/user.service.ts create mode 100644 frontend/src/features/colorSchemeSwitch/colorSchemeSwitch.tsx create mode 100644 frontend/src/features/colorSchemeSwitch/index.ts create mode 100644 frontend/src/features/messagesList/index.ts create mode 100644 frontend/src/features/messagesList/message.tsx create mode 100644 frontend/src/features/messagesList/messagesList.tsx create mode 100644 frontend/src/features/sendMessage/index.ts create mode 100644 frontend/src/features/sendMessage/sendMessage.tsx create mode 100644 frontend/src/shared/assets/icons/index.ts create mode 100644 frontend/src/shared/assets/icons/sendIcon.tsx create mode 100644 frontend/src/shared/assets/icons/sunIcon.tsx create mode 100644 frontend/src/shared/services/http/http.ts create mode 100644 frontend/src/shared/services/http/index.ts create mode 100644 frontend/src/widgets/chat-window/index.ts create mode 100644 frontend/src/widgets/chat-window/window.tsx delete mode 100644 frontend/tailwind.config.ts diff --git a/backend/.env.example b/backend/.env.example index f4edb33..13696fd 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,3 +1,4 @@ +PORT=8000 DATABASE_TYPE=postgres DATABASE_HOST=localhost DATABASE_PORT=3306 diff --git a/backend/package-lock.json b/backend/package-lock.json index b4c55bd..5863dcd 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,12 +9,15 @@ "version": "0.0.1", "license": "UNLICENSED", "dependencies": { - "@nestjs/common": "^11.0.1", + "@nestjs/common": "^11.0.11", "@nestjs/config": "^4.0.1", - "@nestjs/core": "^11.0.1", + "@nestjs/core": "^11.0.11", "@nestjs/platform-express": "^11.0.1", + "@nestjs/platform-socket.io": "^11.0.11", + "@nestjs/platform-ws": "^11.0.11", "@nestjs/swagger": "^11.0.6", "@nestjs/typeorm": "^11.0.0", + "@nestjs/websockets": "^11.0.11", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "mysql2": "^3.13.0", @@ -2407,6 +2410,65 @@ "@nestjs/core": "^11.0.0" } }, + "node_modules/@nestjs/platform-socket.io": { + "version": "11.0.11", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.0.11.tgz", + "integrity": "sha512-+bLrtTSPDX6AxrL9PbR5lEgcEnn6oFzkGpLUcm3Xs9x5OBejzJh1tiWgBGJRQIh3l9iIG8/mQ8hNwufAt8SIcA==", + "license": "MIT", + "dependencies": { + "socket.io": "4.8.1", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/websockets": "^11.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/platform-ws": { + "version": "11.0.11", + "resolved": "https://registry.npmjs.org/@nestjs/platform-ws/-/platform-ws-11.0.11.tgz", + "integrity": "sha512-aIQCEJVHwlRwJPczFShsWEJq7w+VqyAFe1EjG8oxSihJ7sPqT+1jOLgqXabZSAKZNl2sF4++NtxwGZG++5/hlQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1", + "ws": "8.18.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/websockets": "^11.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/platform-ws/node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "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/@nestjs/schematics": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.2.tgz", @@ -2579,6 +2641,29 @@ "typeorm": "^0.3.0" } }, + "node_modules/@nestjs/websockets": { + "version": "11.0.11", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.0.11.tgz", + "integrity": "sha512-9sNNT/kYA534iaFyZ9MrOXKwQFuJArsMXhT6ywVxaWKQ84lVbV/sDmdmJUe9mzUGLPiHMn+m3oDUO9MiLTEKPA==", + "license": "MIT", + "dependencies": { + "iterare": "1.2.1", + "object-hash": "3.0.0", + "tslib": "2.8.1" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0", + "@nestjs/platform-socket.io": "^11.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/platform-socket.io": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2710,6 +2795,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "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" + }, "node_modules/@sqltools/formatter": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", @@ -3134,6 +3225,15 @@ "dev": true, "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": "*" + } + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -3276,7 +3376,6 @@ "version": "22.13.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -4372,6 +4471,15 @@ ], "license": "MIT" }, + "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" + } + }, "node_modules/bin-version": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz", @@ -5486,6 +5594,104 @@ "node": ">= 0.8" } }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "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", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", @@ -8725,6 +8931,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -10098,6 +10313,141 @@ "node": ">=8" } }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/sort-keys": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", @@ -11414,7 +11764,6 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "devOptional": true, "license": "MIT" }, "node_modules/universalify": { @@ -11910,6 +12259,27 @@ "dev": true, "license": "ISC" }, + "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/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/backend/package.json b/backend/package.json index 6e55d08..8507a27 100644 --- a/backend/package.json +++ b/backend/package.json @@ -20,12 +20,15 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { - "@nestjs/common": "^11.0.1", + "@nestjs/common": "^11.0.11", "@nestjs/config": "^4.0.1", - "@nestjs/core": "^11.0.1", + "@nestjs/core": "^11.0.11", "@nestjs/platform-express": "^11.0.1", + "@nestjs/platform-socket.io": "^11.0.11", + "@nestjs/platform-ws": "^11.0.11", "@nestjs/swagger": "^11.0.6", "@nestjs/typeorm": "^11.0.0", + "@nestjs/websockets": "^11.0.11", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "mysql2": "^3.13.0", diff --git a/backend/src/controllers/message/message.controller.ts b/backend/src/controllers/message/message.controller.ts index d8b1697..0f157aa 100644 --- a/backend/src/controllers/message/message.controller.ts +++ b/backend/src/controllers/message/message.controller.ts @@ -1,10 +1,14 @@ import Message, { CreateMessageDTO } from '@/entities/message'; +import MessageGateway from '@/shared/gateways/message'; import MessageService from '@/shared/services/message'; import { Body, Controller, Get, Post } from '@nestjs/common'; @Controller('message') export class MessageController { - constructor(private messageService: MessageService) {} + constructor( + private messageService: MessageService, + private messageGateway: MessageGateway, + ) {} @Get() async getTopOfHistory(): Promise { @@ -13,6 +17,8 @@ export class MessageController { @Post() async send(@Body() message: CreateMessageDTO): Promise { - return await this.messageService.addMessage(message); + const newMessage = await this.messageService.addMessage(message); + this.messageGateway.sendMessage(newMessage); + return newMessage; } } diff --git a/backend/src/entities/message/message.ts b/backend/src/entities/message/message.ts index 67f41c8..9475960 100644 --- a/backend/src/entities/message/message.ts +++ b/backend/src/entities/message/message.ts @@ -5,6 +5,9 @@ export class Message { @PrimaryGeneratedColumn() id: number; + @Column() + text: string; + @Column() timeOfSend: string; diff --git a/backend/src/main.ts b/backend/src/main.ts index 0e88290..8d7584b 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -1,12 +1,15 @@ import { NestFactory } from '@nestjs/core'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { ValidationPipe } from '@nestjs/common'; +import { WsAdapter } from '@nestjs/platform-ws'; -import AppModule from './modules/app'; +import AppModule from '@/modules/app'; async function bootstrap() { const app = await NestFactory.create(AppModule); + app.useGlobalPipes(new ValidationPipe()); + app.useWebSocketAdapter(new WsAdapter(app)); const options = new DocumentBuilder() .setTitle('Chat') diff --git a/backend/src/modules/app/app.module.ts b/backend/src/modules/app/app.module.ts index 46af0e4..d174ca9 100644 --- a/backend/src/modules/app/app.module.ts +++ b/backend/src/modules/app/app.module.ts @@ -4,13 +4,14 @@ import { ConfigModule } from '@nestjs/config'; import config from '@/configuration/configuration'; import databaseConfig from '@/configuration/database'; -import MessageModule from '../message'; + +import MessageModule from '@/modules/message'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, - load: [config], + load: [config, databaseConfig], }), TypeOrmModule.forRoot(databaseConfig()), diff --git a/backend/src/modules/message/message.module.ts b/backend/src/modules/message/message.module.ts index dfb019c..d2a2ca6 100644 --- a/backend/src/modules/message/message.module.ts +++ b/backend/src/modules/message/message.module.ts @@ -1,5 +1,6 @@ import MessageController from '@/controllers/message'; import Message from '@/entities/message'; +import MessageGateway from '@/shared/gateways/message'; import MessageService from '@/shared/services/message'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; @@ -7,6 +8,6 @@ import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [TypeOrmModule.forFeature([Message])], controllers: [MessageController], - providers: [MessageService], + providers: [MessageService, MessageGateway], }) export class MessageModule {} diff --git a/backend/src/shared/gateways/message/index.ts b/backend/src/shared/gateways/message/index.ts new file mode 100644 index 0000000..4aec486 --- /dev/null +++ b/backend/src/shared/gateways/message/index.ts @@ -0,0 +1,3 @@ +import { MessageGateway } from './message.gateway'; + +export default MessageGateway; diff --git a/backend/src/shared/gateways/message/message.gateway.ts b/backend/src/shared/gateways/message/message.gateway.ts new file mode 100644 index 0000000..d6d1f46 --- /dev/null +++ b/backend/src/shared/gateways/message/message.gateway.ts @@ -0,0 +1,17 @@ +import Message from '@/entities/message'; +import { OnGatewayConnection, WebSocketGateway } from '@nestjs/websockets'; +import { Socket } from 'socket.io'; + +@WebSocketGateway(8002) +export class MessageGateway implements OnGatewayConnection { + private clients: Socket[] = []; + + handleConnection(client: Socket) { + this.clients.push(client); + } + + sendMessage = (message: Message) => { + console.log(message, this.clients.length); + this.clients.forEach((client) => client.send(JSON.stringify(message))); + }; +} diff --git a/docker-compose-all.yml b/docker-compose-all.yml new file mode 100644 index 0000000..633e57f --- /dev/null +++ b/docker-compose-all.yml @@ -0,0 +1,50 @@ +services: + postgres: + container_name: postgres_container + image: postgres + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: pass2postgres + PGDATA: /data/postgres + volumes: + - postgres:/data/postgres + - ./create_database.sql:/docker-entrypoint-initdb.d/init.sql + # ports: + # - "5432:5432" + networks: + - postgres + restart: unless-stopped + + backend: + build: ./backend + environment: + DATABASE_HOST: postgres + DATABASE_PORT: 5432 + DATABASE_USER: postgres + DATABASE_PASSWORD: pass2postgres + DATABASE_DATABASE: chat + WEBSOCKETS_PORT: 8001 + # ports: + # - "5000:5000" + depends_on: + - postgres + + frontend: + build: ./frontend + environment: + NEXT_PUBLIC_BASE_PROTOCOL: http + NEXT_PUBLIC_BASE_DOMAIN: backend + NEXT_PUBLIC_BASE_PORT: 3000 + NEXT_PUBLIC_WS_URL: ws://backend:8002/ + NEXT_PUBLIC_API_PATTERN: / + ports: + - "3000:3000" + depends_on: + - backend + +networks: + postgres: + driver: bridge + +volumes: + postgres: diff --git a/frontend/.env b/frontend/.env index f9b28d2..e69de29 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1 +0,0 @@ -NEXT_PUBLIC_API_PATTERN=/api \ No newline at end of file diff --git a/frontend/.env.development b/frontend/.env.development index 7a2278a..a31d591 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -1,9 +1,11 @@ -BACKEND_PROTOCOL=http -BACKEND_DOMAIN=127.0.0.1 -BACKEND_PORT=8000 +BACKEND_API_PROTOCOL=http +BACKEND_API_DOMAIN=localhost +BACKEND_API_PORT=8000 -BASE_PROTOCOL=http -BASE_DOMAIN=127.0.0.1 -BASE_PORT=3000 +NEXT_PUBLIC_BASE_PROTOCOL=http +NEXT_PUBLIC_BASE_DOMAIN=localhost +NEXT_PUBLIC_BASE_PORT=3000 -NEXT_PUBLIC_BASE_URL=http://127.0.0.1:3000 \ No newline at end of file +NEXT_PUBLIC_WS_URL=ws://127.0.0.1:8002/ + +NEXT_PUBLIC_API_PATTERN=/api/ \ No newline at end of file diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 22bb1c0..e71ba0b 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -4,20 +4,21 @@ const nextConfig = { { source: "/api/:path*", destination: - `${process.env.BACKEND_PROTOCOL}://` + - `${process.env.BACKEND_DOMAIN}:${process.env.BACKEND_PORT}/:path*`, + `${process.env.BACKEND_API_PROTOCOL}://` + + `${process.env.BACKEND_API_DOMAIN}:${process.env.BACKEND_API_PORT}/:path*`, }, ]; }, images: { remotePatterns: [ { - protocol: process.env.BASE_PROTOCOL, - hostname: process.env.BASE_DOMAIN, - port: process.env.BASE_PORT, + protocol: process.env.NEXT_PUBLIC_BASE_PROTOCOL, + hostname: process.env.NEXT_PUBLIC_BASE_DOMAIN, + port: process.env.NEXT_PUBLIC_BASE_PORT, }, ], }, + devIndicators: false, }; export default nextConfig; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 57e54d5..2860e62 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,10 +8,14 @@ "name": "chat", "version": "0.1.0", "dependencies": { + "@tailwindcss/vite": "^4.0.0", "next": "15.2.2", "next-themes": "^0.4.6", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "swr": "^2.3.3", + "uuid": "^11.1.0", + "yup": "^1.6.1" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -21,7 +25,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.2.2", - "tailwindcss": "^4", + "tailwindcss": "^4.0.0", "typescript": "^5" } }, @@ -71,6 +75,431 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", @@ -998,6 +1427,272 @@ "win32" ] }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz", + "integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz", + "integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz", + "integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz", + "integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz", + "integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz", + "integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz", + "integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz", + "integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz", + "integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz", + "integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz", + "integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz", + "integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz", + "integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz", + "integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz", + "integrity": "sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz", + "integrity": "sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz", + "integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz", + "integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz", + "integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1031,7 +1726,6 @@ "version": "4.0.14", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.14.tgz", "integrity": "sha512-Ux9NbFkKWYE4rfUFz6M5JFLs/GEYP6ysxT8uSyPn6aTbh2K3xDE1zz++eVK4Vwx799fzMF8CID9sdHn4j/Ab8w==", - "dev": true, "license": "MIT", "dependencies": { "enhanced-resolve": "^5.18.1", @@ -1039,11 +1733,16 @@ "tailwindcss": "4.0.14" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.14.tgz", + "integrity": "sha512-92YT2dpt671tFiHH/e1ok9D987N9fHD5VWoly1CdPD/Cd1HMglvZwP3nx2yTj2lbXDAHt8QssZkxTLCCTNL+xw==", + "license": "MIT" + }, "node_modules/@tailwindcss/oxide": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.14.tgz", "integrity": "sha512-M8VCNyO/NBi5vJ2cRcI9u8w7Si+i76a7o1vveoGtbbjpEYJZYiyc7f2VGps/DqawO56l3tImIbq2OT/533jcrA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 10" @@ -1069,7 +1768,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1086,7 +1784,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1103,7 +1800,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1120,7 +1816,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1137,7 +1832,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1154,7 +1848,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1171,7 +1864,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1188,7 +1880,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1205,7 +1896,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1222,7 +1912,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1239,7 +1928,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1264,6 +1952,28 @@ "tailwindcss": "4.0.14" } }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.14.tgz", + "integrity": "sha512-92YT2dpt671tFiHH/e1ok9D987N9fHD5VWoly1CdPD/Cd1HMglvZwP3nx2yTj2lbXDAHt8QssZkxTLCCTNL+xw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/vite": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.0.tgz", + "integrity": "sha512-4uukMiU9gHui8KMPMdWic5SP1O/tmQ1NFSRNrQWmcop5evAVl/LZ6/LuWL3quEiecp2RBcRWwqJrG+mFXlRlew==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "^4.0.0", + "@tailwindcss/oxide": "^4.0.0", + "lightningcss": "^1.29.1", + "tailwindcss": "4.0.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -1279,7 +1989,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -1300,7 +2009,7 @@ "version": "20.17.24", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz", "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -2187,11 +2896,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -2236,7 +2953,6 @@ "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -2420,6 +3136,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3000,6 +3757,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3171,7 +3943,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -3758,7 +4529,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -3882,7 +4652,6 @@ "version": "1.29.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==", - "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -3914,7 +4683,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3935,7 +4703,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3956,7 +4723,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3977,7 +4743,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3998,7 +4763,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4019,7 +4783,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4040,7 +4803,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4061,7 +4823,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4082,7 +4843,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4103,7 +4863,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4621,7 +5380,6 @@ "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4668,6 +5426,12 @@ "react-is": "^16.13.1" } }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4823,6 +5587,45 @@ "node": ">=0.10.0" } }, + "node_modules/rollup": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz", + "integrity": "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.35.0", + "@rollup/rollup-android-arm64": "4.35.0", + "@rollup/rollup-darwin-arm64": "4.35.0", + "@rollup/rollup-darwin-x64": "4.35.0", + "@rollup/rollup-freebsd-arm64": "4.35.0", + "@rollup/rollup-freebsd-x64": "4.35.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", + "@rollup/rollup-linux-arm-musleabihf": "4.35.0", + "@rollup/rollup-linux-arm64-gnu": "4.35.0", + "@rollup/rollup-linux-arm64-musl": "4.35.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", + "@rollup/rollup-linux-riscv64-gnu": "4.35.0", + "@rollup/rollup-linux-s390x-gnu": "4.35.0", + "@rollup/rollup-linux-x64-gnu": "4.35.0", + "@rollup/rollup-linux-x64-musl": "4.35.0", + "@rollup/rollup-win32-arm64-msvc": "4.35.0", + "@rollup/rollup-win32-ia32-msvc": "4.35.0", + "@rollup/rollup-win32-x64-msvc": "4.35.0", + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5328,23 +6131,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz", + "integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/tailwindcss": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.14.tgz", - "integrity": "sha512-92YT2dpt671tFiHH/e1ok9D987N9fHD5VWoly1CdPD/Cd1HMglvZwP3nx2yTj2lbXDAHt8QssZkxTLCCTNL+xw==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0.tgz", + "integrity": "sha512-ULRPI3A+e39T7pSaf1xoi58AqqJxVCLg8F/uM5A3FadUbnyDTgltVnXJvdkTjwCOGA6NazqHVcwPJC5h2vRYVQ==", "license": "MIT" }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", @@ -5403,6 +6223,12 @@ "node": ">=8.0" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", @@ -5448,6 +6274,18 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -5563,7 +6401,7 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/uri-js": { @@ -5576,6 +6414,100 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vite": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.2.tgz", + "integrity": "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5703,6 +6635,18 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yup": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.6.1.tgz", + "integrity": "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==", + "license": "MIT", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } } } } diff --git a/frontend/package.json b/frontend/package.json index 2cf9841..a25f6ed 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,10 +9,14 @@ "lint": "next lint" }, "dependencies": { + "@tailwindcss/vite": "^4.0.0", "next": "15.2.2", "next-themes": "^0.4.6", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "swr": "^2.3.3", + "uuid": "^11.1.0", + "yup": "^1.6.1" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -22,7 +26,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.2.2", - "tailwindcss": "^4", + "tailwindcss": "^4.0.0", "typescript": "^5" } } diff --git a/frontend/postcss.config.mjs b/frontend/postcss.config.mjs index c7bcb4b..61e3684 100644 --- a/frontend/postcss.config.mjs +++ b/frontend/postcss.config.mjs @@ -1,5 +1,7 @@ const config = { - plugins: ["@tailwindcss/postcss"], + plugins: { + "@tailwindcss/postcss": {}, + }, }; export default config; diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 322e869..8276688 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -1,14 +1,32 @@ -@tailwind base; +/* @tailwind base; @tailwind components; -@tailwind utilities; +@tailwind utilities; */ +@import "tailwindcss"; :root { + font-size: calc((100vw / 1920) * 20); + + --color-col1: #fb4934; + --color-col2: #b8bb26; + --color-col3: #fabd2f; + --color-col4: #83a598; + --color-col5: #d3869b; + --color-col6: #8ec07c; + --color-col7: #a89986; + --color-col8: #fe8019; +} + +@theme { --color-bg0: #fbf1c7; --color-bg1: #ebdbb2; + --color-bg2: #d5c4a1; + --color-bg3: #bdae93; --color-bg4: #a89984; --color-fg0: #282828; --color-fg1: #3c3836; + --color-fg2: #504945; + --color-fg3: #665c54; --color-fg4: #7c6f64; --color-ac0: #83a598; @@ -16,18 +34,19 @@ --color-ac2: #8ec07c; --color-err: #cc241d; - - --app-width: 70%; - font-size: calc((100vw / 1920) * 20); } [data-theme="dark"] { --color-bg0: #282828; --color-bg1: #3c3836; + --color-bg2: #504945; + --color-bg3: #665c54; --color-bg4: #7c6f64; --color-fg0: #fbf1c7; --color-fg1: #ebdbb2; + --color-fg2: #d5c4a1; + --color-fg3: #bdae93; --color-fg4: #a89984; --color-ac0: #076678; @@ -35,8 +54,19 @@ --color-ac2: #427b58; --color-err: #cc241d; + + --color-col1: #9d0006; + --color-col2: #79740e; + --color-col3: #b57614; + --color-col4: #076678; + --color-col5: #8f3f71; + --color-col6: #427b58; + --color-col7: #7c6f65; + --color-col8: #af3a03; } +@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); + html, body { padding: 0; @@ -59,10 +89,6 @@ body * { scrollbar-width: thin; } -audio::-webkit-media-controls-panel { - background-color: var(--color-bg1); -} - @media (max-width: 1024px) { :root { font-size: calc((100vw / 1920) * 56); @@ -74,4 +100,4 @@ audio::-webkit-media-controls-panel { :root { font-size: calc((100vw / 1920) * 64); } -} \ No newline at end of file +} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 353f3e3..16b2dcb 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,6 +1,6 @@ import { Roboto } from "next/font/google"; -import "./globals.css"; import { ThemeProvider } from "next-themes"; +import "./globals.css"; const roboto = Roboto({ subsets: ["latin"] }); @@ -12,7 +12,7 @@ export default function RootLayout({ return ( - + {children} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index fd5c1cf..aac9b2a 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,3 +1,4 @@ +import ChatWindow from "@/widgets/chat-window"; import { Metadata } from "next"; export const metadata: Metadata = { @@ -6,5 +7,9 @@ export const metadata: Metadata = { }; export default async function Root() { - return <>; + return ( +
+ +
+ ); } diff --git a/frontend/src/entities/message/index.ts b/frontend/src/entities/message/index.ts new file mode 100644 index 0000000..650f96f --- /dev/null +++ b/frontend/src/entities/message/index.ts @@ -0,0 +1,5 @@ +import { MessageService } from "./message.service"; + +export { type IMessage, messageSchema, messagesSchema } from "./message.schema"; + +export default MessageService; diff --git a/frontend/src/entities/message/message.schema.ts b/frontend/src/entities/message/message.schema.ts new file mode 100644 index 0000000..eba178e --- /dev/null +++ b/frontend/src/entities/message/message.schema.ts @@ -0,0 +1,11 @@ +import { object, string, number, InferType, array } from "yup"; + +export const messageSchema = object({ + id: number().required().positive().integer(), + text: string().required(), + timeOfSend: string().required(), + sender: string().required(), +}); +export const messagesSchema = array(messageSchema); + +export type IMessage = InferType; diff --git a/frontend/src/entities/message/message.service.ts b/frontend/src/entities/message/message.service.ts new file mode 100644 index 0000000..e94e5f7 --- /dev/null +++ b/frontend/src/entities/message/message.service.ts @@ -0,0 +1,16 @@ +import HTTPService from "@/shared/services/http"; +import { messageSchema, messagesSchema } from "./message.schema"; + +export abstract class MessageService { + public static getTopOfHistory = () => + HTTPService.get(`message`, messagesSchema); + + public static sendMessage = (message: string, senderUUID: string) => + HTTPService.post(`message`, messageSchema, { + body: { + text: message, + timeOfSend: new Date().toISOString(), + sender: senderUUID, + }, + }); +} diff --git a/frontend/src/entities/user/index.ts b/frontend/src/entities/user/index.ts new file mode 100644 index 0000000..a896706 --- /dev/null +++ b/frontend/src/entities/user/index.ts @@ -0,0 +1,5 @@ +import { UserService } from "./user.service"; + +export { type IUser, userSchema } from "./user.schema"; + +export default UserService; diff --git a/frontend/src/entities/user/user.schema.ts b/frontend/src/entities/user/user.schema.ts new file mode 100644 index 0000000..8b82835 --- /dev/null +++ b/frontend/src/entities/user/user.schema.ts @@ -0,0 +1,8 @@ +import { object, string, number, InferType } from "yup"; + +export const userSchema = object({ + uuid: string().uuid().required(), + color: number().min(1).max(8).required(), +}); + +export type IUser = InferType; diff --git a/frontend/src/entities/user/user.service.ts b/frontend/src/entities/user/user.service.ts new file mode 100644 index 0000000..35fb9aa --- /dev/null +++ b/frontend/src/entities/user/user.service.ts @@ -0,0 +1,25 @@ +import { v1 as uuidV1 } from "uuid"; +import { IUser } from "./user.schema"; + +export abstract class UserService { + private static pickedColors: IUser["color"][] = []; + + public static generateMe = (): IUser => ({ + uuid: uuidV1(), + color: 8, + }); + + public static generateUser = (uuid: IUser["uuid"]): IUser => { + const newColor = Array.from(Array(7).keys()) + .map((i) => i + 1) + .reduce( + (p, c) => (!p && !this.pickedColors.includes(c) ? c : p), + undefined as number | undefined + ); + this.pickedColors.push(newColor ?? 1); + return { + uuid: uuid, + color: newColor ?? 1, + }; + }; +} diff --git a/frontend/src/features/colorSchemeSwitch/colorSchemeSwitch.tsx b/frontend/src/features/colorSchemeSwitch/colorSchemeSwitch.tsx new file mode 100644 index 0000000..a3064ce --- /dev/null +++ b/frontend/src/features/colorSchemeSwitch/colorSchemeSwitch.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { SunIcon } from "@/shared/assets/icons"; +import { useTheme } from "next-themes"; + +export const ColorSchemeSwitch = () => { + const { theme, setTheme } = useTheme(); + + return ( + <> + setTheme(theme == "light" ? "dark" : "light")} + /> + + ); +}; diff --git a/frontend/src/features/colorSchemeSwitch/index.ts b/frontend/src/features/colorSchemeSwitch/index.ts new file mode 100644 index 0000000..11d2855 --- /dev/null +++ b/frontend/src/features/colorSchemeSwitch/index.ts @@ -0,0 +1,3 @@ +import { ColorSchemeSwitch } from "./colorSchemeSwitch"; + +export default ColorSchemeSwitch; diff --git a/frontend/src/features/messagesList/index.ts b/frontend/src/features/messagesList/index.ts new file mode 100644 index 0000000..de54d42 --- /dev/null +++ b/frontend/src/features/messagesList/index.ts @@ -0,0 +1,3 @@ +import { MessagesList } from "./messagesList"; + +export default MessagesList; diff --git a/frontend/src/features/messagesList/message.tsx b/frontend/src/features/messagesList/message.tsx new file mode 100644 index 0000000..42a6125 --- /dev/null +++ b/frontend/src/features/messagesList/message.tsx @@ -0,0 +1,28 @@ +import { IMessage } from "@/entities/message"; +import { IUser } from "@/entities/user"; + +export const Message = ({ + message, + color, + align = "right", +}: { + message: IMessage; + color: IUser["color"] | undefined; + align?: "left" | "right"; +}) => { + return ( +
+ {message.text} +
+ ); +}; diff --git a/frontend/src/features/messagesList/messagesList.tsx b/frontend/src/features/messagesList/messagesList.tsx new file mode 100644 index 0000000..47768dc --- /dev/null +++ b/frontend/src/features/messagesList/messagesList.tsx @@ -0,0 +1,73 @@ +"use client"; + +import MessageService from "@/entities/message"; +import UserService, { IUser } from "@/entities/user"; +import { useEffect, useRef, useState } from "react"; +import useSWR from "swr"; +import { Message } from "./message"; + +export const MessagesList = () => { + const { data: users, mutate: mutateUsers } = useSWR("users"); + const { data: messages, mutate: mutateMessages } = useSWR( + "messages", + MessageService.getTopOfHistory, + { revalidateOnFocus: false } + ); + const { data: me } = useSWR("me"); + const [websocket, updateWebSocket] = useState(); + const bottomRef = useRef(null); + + const getUserColor = (userUUID: IUser["uuid"]) => + users?.find((user) => user.uuid === userUUID)?.color; + + useEffect(() => { + bottomRef.current?.scrollIntoView({ behavior: "smooth" }); + const newUsers: IUser[] = []; + messages?.forEach((message) => { + if ( + !users?.concat(newUsers).find((user) => user.uuid === message.sender) + ) { + const newUser = UserService.generateUser(message.sender); + newUsers.push(newUser); + } + }); + mutateUsers([...(users ?? []), ...newUsers]); + }, [messages]); + + useEffect(() => { + if (!websocket && messages && process.env.NEXT_PUBLIC_WS_URL) { + const websocket = new WebSocket(process.env.NEXT_PUBLIC_WS_URL); + websocket.onmessage = (event) => { + mutateMessages([...(messages ?? []), JSON.parse(event.data)], { + revalidate: false, + }); + }; + websocket.onclose = function () { + setTimeout(() => updateWebSocket(undefined), 1000); + }; + updateWebSocket(websocket); + } + return () => websocket?.close(); + }, [websocket, messages]); + + return ( +
+ {messages?.map((message) => ( +
+ +
+ ))} +
+
+ ); +}; diff --git a/frontend/src/features/sendMessage/index.ts b/frontend/src/features/sendMessage/index.ts new file mode 100644 index 0000000..403229d --- /dev/null +++ b/frontend/src/features/sendMessage/index.ts @@ -0,0 +1,3 @@ +import { SendMessage } from "./sendMessage"; + +export default SendMessage; diff --git a/frontend/src/features/sendMessage/sendMessage.tsx b/frontend/src/features/sendMessage/sendMessage.tsx new file mode 100644 index 0000000..c2b6dfa --- /dev/null +++ b/frontend/src/features/sendMessage/sendMessage.tsx @@ -0,0 +1,41 @@ +"use client"; + +import MessageService from "@/entities/message"; +import UserService from "@/entities/user"; +import { useState } from "react"; +import useSWR from "swr"; + +export const SendMessage = () => { + const [message, updateMessage] = useState(""); + const { data: me } = useSWR(`me`, UserService.generateMe); + + return ( +
+ updateMessage(e.target.value)} + /> + +
+ ); +}; diff --git a/frontend/src/shared/assets/icons/index.ts b/frontend/src/shared/assets/icons/index.ts new file mode 100644 index 0000000..c6571c8 --- /dev/null +++ b/frontend/src/shared/assets/icons/index.ts @@ -0,0 +1,2 @@ +export { SunIcon } from "./sunIcon"; +export { SendIcon } from "./sendIcon"; diff --git a/frontend/src/shared/assets/icons/sendIcon.tsx b/frontend/src/shared/assets/icons/sendIcon.tsx new file mode 100644 index 0000000..4a106cc --- /dev/null +++ b/frontend/src/shared/assets/icons/sendIcon.tsx @@ -0,0 +1,33 @@ +export const SendIcon = ({ + className, + onClick, +}: { + className?: string; + onClick?: () => void; +}) => { + return ( + + + + + {" "} + {" "} + + + ); +}; diff --git a/frontend/src/shared/assets/icons/sunIcon.tsx b/frontend/src/shared/assets/icons/sunIcon.tsx new file mode 100644 index 0000000..cd0ef90 --- /dev/null +++ b/frontend/src/shared/assets/icons/sunIcon.tsx @@ -0,0 +1,31 @@ +export const SunIcon = ({ + className, + onClick, +}: { + className?: string; + onClick?: () => void; +}) => { + return ( + + + + ); +}; diff --git a/frontend/src/shared/services/http/http.ts b/frontend/src/shared/services/http/http.ts new file mode 100644 index 0000000..5117bb0 --- /dev/null +++ b/frontend/src/shared/services/http/http.ts @@ -0,0 +1,95 @@ +import { AnySchema } from "yup"; + +export type RequestCacheOptions = { + cache?: RequestCache; + next?: NextFetchRequestConfig; +}; + +type GetRequestOptions = RequestCacheOptions & { + headers?: HeadersInit; +}; + +type RequestOptions = GetRequestOptions & { + body?: BodyInit | object; + stringify?: boolean; +}; + +export abstract class HTTPService { + private static deepUndefinedToNull(o?: object): object | undefined { + if (Array.isArray(o)) return o; + if (o) + return Object.fromEntries( + Object.entries(o).map(([k, v]) => { + if (v === undefined) return [k, null]; + if (typeof v === "object") return [k, this.deepUndefinedToNull(v)]; + return [k, v]; + }) + ); + } + + public static async request( + method: "GET" | "POST" | "PUT" | "DELETE", + url: string, + schema: Y, + options?: RequestOptions + ) { + return await fetch( + `${process.env.NEXT_PUBLIC_BASE_PROTOCOL}://` + + `${process.env.NEXT_PUBLIC_BASE_DOMAIN}:` + + `${process.env.NEXT_PUBLIC_BASE_PORT}` + + `${process.env.NEXT_PUBLIC_API_PATTERN}${url}`, + { + method: method, + headers: { + accept: "application/json", + ...((options?.stringify ?? true) != true + ? {} + : { "Content-Type": "application/json" }), + ...options?.headers, + }, + body: + (options?.stringify ?? true) != true + ? (options?.body as BodyInit) + : JSON.stringify( + this.deepUndefinedToNull(options?.body as object | undefined) + ), + cache: options?.cache ?? options?.next ? undefined : "no-cache", + next: options?.next ?? {}, + } + ) + .then((r) => { + if (r && r.ok) return r; + else throw Error("Response ok = false"); + }) + .then((r) => r.json()) + .then(async (d) => await schema.validate(d)) + .catch((e) => { + console.error(e); + return null; + }); + } + + public static async get( + url: string, + schema: Y, + options?: GetRequestOptions + ) { + return await this.request("GET", url, schema, options); + } + + public static async post( + url: string, + schema: Y, + options?: RequestOptions + ) { + return await this.request("POST", url, schema, options); + } + + public static async put( + url: string, + schema: Y, + options?: RequestOptions + ) { + return await this.request("PUT", url, schema, options); + } +} diff --git a/frontend/src/shared/services/http/index.ts b/frontend/src/shared/services/http/index.ts new file mode 100644 index 0000000..6e7b707 --- /dev/null +++ b/frontend/src/shared/services/http/index.ts @@ -0,0 +1,3 @@ +import { HTTPService } from "./http"; + +export default HTTPService; diff --git a/frontend/src/widgets/chat-window/index.ts b/frontend/src/widgets/chat-window/index.ts new file mode 100644 index 0000000..724088b --- /dev/null +++ b/frontend/src/widgets/chat-window/index.ts @@ -0,0 +1,3 @@ +import { ChatWindow } from "./window"; + +export default ChatWindow; diff --git a/frontend/src/widgets/chat-window/window.tsx b/frontend/src/widgets/chat-window/window.tsx new file mode 100644 index 0000000..ddf4aea --- /dev/null +++ b/frontend/src/widgets/chat-window/window.tsx @@ -0,0 +1,25 @@ +import ColorSchemeSwitch from "@/features/colorSchemeSwitch"; +import MessagesList from "@/features/messagesList"; +import SendMessage from "@/features/sendMessage"; + +export const ChatWindow = () => { + return ( +
+ +
+ + +
+
+ ); +}; diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts deleted file mode 100644 index 5a04de4..0000000 --- a/frontend/tailwind.config.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Config } from "tailwindcss"; - -const config: Config = { - content: [ - "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", - "./src/components/**/*.{js,ts,jsx,tsx,mdx}", - "./src/app/**/*.{js,ts,jsx,tsx,mdx}", - "./src/**/*.{js,ts,jsx,tsx,mdx}", - ], - theme: { - extend: { - colors: { - bg0: "var(--color-bg0)", - bg1: "var(--color-bg1)", - bg4: "var(--color-bg4)", - fg0: "var(--color-fg0)", - fg1: "var(--color-fg1)", - fg4: "var(--color-fg4)", - ac0: "var(--color-ac0)", - ac1: "var(--color-ac1)", - ac2: "var(--color-ac2)", - err: "var(--color-err)", - }, - animation: { - fadeIn: "fadeIn 0.25s ease-in-out", - fadeOut: "fadeOut 0.25s ease-in-out", - }, - keyframes: () => ({ - fadeIn: { - "0%": { opacity: "0" }, - "100%": { opacity: "1" }, - }, - fadeOut: { - "0%": { opacity: "1" }, - "100%": { opacity: "0" }, - }, - }), - }, - screens: { - tb: "640px", - lp: "1024px", - dsk: "1280px", - }, - }, - plugins: [], -}; -export default config;