diff --git a/package-lock.json b/package-lock.json index 6478421..608ceaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,15 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.7.7", + "express": "^4.21.0", + "jsonwebtoken": "^9.0.2", "leaflet": "^1.9.4", "leaflet-defaulticon-compatibility": "^0.1.2", "qrcode.react": "^4.0.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-image-crop": "^11.0.7", "react-leaflet": "^4.2.1", "react-router-dom": "^6.26.2", "react-scripts": "5.0.1", @@ -5862,6 +5866,31 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -6384,6 +6413,12 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -7792,6 +7827,15 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -13417,6 +13461,28 @@ "node": ">=0.10.0" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -13432,6 +13498,27 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -13592,6 +13679,42 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -13604,6 +13727,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -16015,6 +16144,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -16395,6 +16530,15 @@ "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==", "license": "MIT" }, + "node_modules/react-image-crop": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/react-image-crop/-/react-image-crop-11.0.7.tgz", + "integrity": "sha512-ZciKWHDYzmm366JDL18CbrVyjnjH0ojufGDmScfS4ZUqLHg4nm6ATY+K62C75W4ZRNt4Ii+tX0bSjNk9LQ2xzQ==", + "license": "ISC", + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/package.json b/package.json index fb34334..29415a1 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,15 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.7.7", + "express": "^4.21.0", + "jsonwebtoken": "^9.0.2", "leaflet": "^1.9.4", "leaflet-defaulticon-compatibility": "^0.1.2", "qrcode.react": "^4.0.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-image-crop": "^11.0.7", "react-leaflet": "^4.2.1", "react-router-dom": "^6.26.2", "react-scripts": "5.0.1", diff --git a/public/index.html b/public/index.html index 23d1da4..bd9c935 100644 --- a/public/index.html +++ b/public/index.html @@ -28,7 +28,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + YANDEX ZAKLADKI diff --git a/public/logo192.png b/public/logo192.png index fc44b0a..a7eba36 100644 Binary files a/public/logo192.png and b/public/logo192.png differ diff --git a/public/logo512.png b/public/logo512.png index a4e47a6..a7eba36 100644 Binary files a/public/logo512.png and b/public/logo512.png differ diff --git a/src/UserAccount.scss b/src/UserAccount.scss index eb5cf87..4a14435 100644 --- a/src/UserAccount.scss +++ b/src/UserAccount.scss @@ -1,87 +1,140 @@ .user-account { - padding: 20px; - background-color: #f4f4f4; - border-radius: 10px; - max-width: 400px; - margin: 0 auto; - - h1 { - text-align: center; + padding: 40px; + background-color: #ffffff; + border-radius: 15px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + max-width: 600px; + margin: 60px auto; + font-family: 'Arial', sans-serif; + + h1 { + text-align: center; + font-size: 28px; + color: #333; + margin-bottom: 30px; + font-weight: 500; + } + + .user-info, .edit-form { + display: flex; + flex-direction: column; + gap: 20px; + + .avatar-container { + display: flex; + justify-content: flex-start; margin-bottom: 20px; + + .avatar { + width: 100px; + height: 100px; + border-radius: 50%; + object-fit: cover; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + } + + .avatar-placeholder { + width: 100px; + height: 100px; + background-color: #3498db; + color: white; + font-size: 36px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + } } - - .user-info, .edit-form { + + p, input { + font-size: 18px; + color: #333; + margin-bottom: 15px; + padding: 10px 15px; + border: 1px solid #ddd; + border-radius: 8px; + background-color: #f9f9f9; + outline: none; + transition: border-color 0.3s ease; + + &:focus { + border-color: #3498db; + } + } + + label { display: flex; flex-direction: column; - gap: 10px; - - label { - display: flex; - justify-content: space-between; + font-size: 16px; + color: #666; + margin-bottom: 15px; + + input[type="text"], input[type="email"], input[type="tel"], input[type="file"] { + padding: 10px; font-size: 16px; + border-radius: 8px; + background-color: #f9f9f9; + border: 1px solid #ddd; + margin-top: 10px; + transition: border-color 0.3s ease; + + &:focus { + border-color: #3498db; + } } - - input { - padding: 8px; - font-size: 14px; - border: 1px solid #ccc; - border-radius: 5px; - } - - p { - font-size: 18px; - margin: 10px 0; - } - - .buttons { - display: flex; - justify-content: space-between; - - button { - padding: 10px 20px; - font-size: 14px; - cursor: pointer; - border: none; - border-radius: 5px; - background-color: #3498db; - color: white; - transition: background-color 0.3s ease; - + } + + .buttons { + display: flex; + justify-content: space-between; + + button { + padding: 14px 30px; + font-size: 16px; + cursor: pointer; + border: none; + border-radius: 8px; + background-color: #3498db; + color: white; + transition: background-color 0.3s ease, transform 0.2s ease; + + &:hover { + background-color: #2980b9; + transform: scale(1.03); + } + + &:last-child { + background-color: #e74c3c; + &:hover { - background-color: #2980b9; - } - - &:last-child { - background-color: #e74c3c; - - &:hover { - background-color: #c0392b; - } + background-color: #c0392b; } } } } - - .edit-button { - margin-top: 20px; - padding: 10px 25px; - font-size: 16px; - font-weight: bold; - cursor: pointer; - background-color: #2ecc71; /* Зелёный цвет */ - color: white; - border: none; - border-radius: 5px; - transition: background-color 0.3s ease, transform 0.2s ease; - - &:hover { - background-color: #27ae60; - transform: scale(1.05); /* Небольшой зум при наведении */ - } - - &:active { - background-color: #1e8449; - transform: scale(0.98); /* Легкий уменьшенный эффект при нажатии */ - } + } + + .edit-button { + margin-top: 30px; + padding: 14px 35px; + font-size: 18px; + font-weight: 600; + cursor: pointer; + background-color: #2ecc71; + color: white; + border: none; + border-radius: 8px; + transition: background-color 0.3s ease, transform 0.3s ease; + + &:hover { + background-color: #27ae60; + transform: scale(1.05); } - } \ No newline at end of file + + &:active { + background-color: #1e8449; + transform: scale(0.98); + } + } +} \ No newline at end of file diff --git a/src/pages/Devices.js b/src/pages/Devices.js index adb5992..28e001f 100644 --- a/src/pages/Devices.js +++ b/src/pages/Devices.js @@ -1,5 +1,6 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { QRCodeCanvas } from "qrcode.react"; +import axios from 'axios'; import '../Devices.css'; const Devices = () => { @@ -7,10 +8,26 @@ const Devices = () => { const [selectedDevice, setSelectedDevice] = useState(null); const [deviceName, setDeviceName] = useState(''); const [deviceStatus, setDeviceStatus] = useState(''); + const [isRegenerating, setIsRegenerating] = useState(false); // отображение генерации токена + const [showFullToken, setShowFullToken] = useState({}); // управление отображением токена + + useEffect(() => { + // получение устройств + const fetchDevices = async () => { + try { + const response = await axios.get('/api/devices'); // потом нужно будет заменить на нашу апишку + setDevices(response.data.devices); + } catch (error) { + console.error('Ошибка при загрузке устройств:', error); + } + }; + + fetchDevices(); + }, []); const addDevice = () => { if (deviceName) { - const newDevice = { name: deviceName, status: deviceStatus || 'Неактивен', id: Date.now() }; + const newDevice = { name: deviceName, status: deviceStatus || 'Неактивен', id: Date.now(), access_key: '' }; setDevices([...devices, newDevice]); setDeviceName(''); setDeviceStatus(''); @@ -38,6 +55,25 @@ const Devices = () => { } }; + // генерация токена + const handleShowFullToken = (deviceId) => { + setShowFullToken(prevState => ({ ...prevState, [deviceId]: !prevState[deviceId] })); + }; + + // пересоздание токена + const handleRegenerateToken = async (deviceId) => { + setIsRegenerating(true); + try { + const response = await axios.post(`/api/devices/${deviceId}/regenerate-token`); // тоже заменить на апишку + setDevices(devices.map(device => + device.id === deviceId ? { ...device, access_key: response.data.access_key } : device + )); + } catch (error) { + console.error('Ошибка при пересоздании токена:', error); + } + setIsRegenerating(false); + }; + const generateQR = (device) => { return ( @@ -65,6 +101,7 @@ const Devices = () => { Название Статус QR Код + Токен устройства Действия @@ -74,8 +111,24 @@ const Devices = () => { {device.name} {device.status} {generateQR(device)} + + {showFullToken[device.id] ? ( + device.access_key + ) : ( + {device.access_key.slice(0, 6)}... + )} + + + ))} diff --git a/src/pages/UserAccount.js b/src/pages/UserAccount.js index 0440cc8..24efdb6 100644 --- a/src/pages/UserAccount.js +++ b/src/pages/UserAccount.js @@ -6,6 +6,7 @@ const UserAccount = () => { username: 'Мамут Рахал', email: 'yatupoidayn@mail.ru', phone: '+666', + avatar: '', // Новое поле для аватара }); const [isEditing, setIsEditing] = useState(false); @@ -19,6 +20,21 @@ const UserAccount = () => { })); }; + // Функция для загрузки аватара + const handleAvatarChange = (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + setFormData((prevData) => ({ + ...prevData, + avatar: reader.result, + })); + }; + reader.readAsDataURL(file); + } + }; + const handleSave = () => { setUserData(formData); setIsEditing(false); @@ -62,6 +78,14 @@ const UserAccount = () => { onChange={handleChange} /> +
@@ -69,6 +93,13 @@ const UserAccount = () => {
) : (
+
+ {userData.avatar ? ( + User Avatar + ) : ( +
A
+ )} +

Имя: {userData.username}

Email: {userData.email}

Телефон: {userData.phone}