diff --git a/index.js b/index.js index b00e396..077cff1 100644 --- a/index.js +++ b/index.js @@ -112,7 +112,6 @@ app.get("/projects", checkAuthorization, async (req, res) => { app.get("/user/settings", checkAuthorization, async (req, res) => { const token = req.cookies.token; - const name = req.params.name; console.log(token); if (!req.isAuth) { @@ -158,6 +157,105 @@ app.get("/user/settings", checkAuthorization, async (req, res) => { }); }); +app.get("/admin/users", checkAuthorization, async (req, res) => { + const token = req.cookies.token; + console.log(token); + + if (!req.isAuth) { + res.redirect("/"); + return; + } + + var options = { + method: "POST", + url: process.env.API_SERVER + "/getusers", + headers: { + "Content-Type": + "multipart/form-data; boundary=---011000010111000001101001", + Authorization: req.cookies.token, + }, + data: '-----011000010111000001101001\r\nContent-Disposition: form-data; name="searchText"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name="page"\r\n\r\n\r\n-----011000010111000001101001--\r\n', + }; + + axios + .request(options) + .then(function (response) { + var templateData = { + API_SERVER: process.env.API_SERVER, + SITE_DOMAIN: process.env.SITE_DOMAIN, + IsAdmin: req.apiResponse.isadmin, + User: response.data.user, + Users: response.data.data, + isAuth: req.isAuth, + }; + + const source = fs.readFileSync( + "static/templates/admin/users.html", + "utf8" + ); + const template = handlebars.compile(source); + const resultT = template(templateData); + res.send(resultT); + }) + .catch(function (error) { + if (error.response && error.response.status === 401) { + res.redirect("/"); + } else { + console.error("Ошибка при отправке GET-запроса:", error); + res.status(500).send("Произошла ошибка при выполнении запроса."); + } + }); +}); + +app.get("/admin/user/:id", checkAuthorization, async (req, res) => { + const token = req.cookies.token; + const id = req.params.id; + console.log(token); + + if (!req.isAuth) { + res.redirect("/"); + return; + } + + var options = { + method: "GET", + url: process.env.API_SERVER + "/user?id=" + id, + headers: { + Authorization: req.cookies.token, + }, + data: '-----011000010111000001101001\r\nContent-Disposition: form-data; name="searchText"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name="page"\r\n\r\n\r\n-----011000010111000001101001--\r\n', + }; + + axios + .request(options) + .then(function (response) { + var templateData = { + API_SERVER: process.env.API_SERVER, + SITE_DOMAIN: process.env.SITE_DOMAIN, + IsAdmin: req.apiResponse.isadmin, + User: response.data.user, + Users: response.data.data, + Projects: response.data.projects, + isAuth: req.isAuth, + }; + + const source = fs.readFileSync( + "static/templates/admin/usersettings.html", + "utf8" + ); + const template = handlebars.compile(source); + const resultT = template(templateData); + res.send(resultT); + }) + .catch(function (error) { + if (error.response && error.response.status === 401) { + res.redirect("/"); + } else { + res.redirect("/admin/users"); + } + }); +}); + app.get("/project/:name", checkAuthorization, async (req, res) => { const token = req.cookies.token; const name = req.params.name; @@ -424,6 +522,58 @@ app.get("/project/:name/tests", checkAuthorization, async (req, res) => { }); }); +app.get("/project/:name/settings", checkAuthorization, async (req, res) => { + const token = req.cookies.token; + const name = req.params.name; + console.log(token); + + if (!req.isAuth) { + res.redirect("/"); + return; + } + + var options = { + method: "GET", + url: process.env.API_SERVER + "/tests?name=" + name, + headers: { + Authorization: req.cookies.token, + }, + data: '-----011000010111000001101001\r\nContent-Disposition: form-data; name="searchText"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name="page"\r\n\r\n\r\n-----011000010111000001101001--\r\n', + }; + + axios + .request(options) + .then(function (response) { + var templateData = { + API_SERVER: process.env.API_SERVER, + SITE_DOMAIN: process.env.SITE_DOMAIN, + IsAdmin: req.apiResponse.isadmin, + Name: name, + User: response.data.user, + Project: response.data.data, + Projects: response.data.projects, + Tests: response.data.tests, + isAuth: req.isAuth, + }; + + const source = fs.readFileSync( + "static/templates/project/settings.html", + "utf8" + ); + const template = handlebars.compile(source); + const resultT = template(templateData); + res.send(resultT); + }) + .catch(function (error) { + if (error.response && error.response.status === 401) { + res.redirect("/"); + } else { + console.error("Ошибка при отправке GET-запроса:", error); + res.status(500).send("Произошла ошибка при выполнении запроса."); + } + }); +}); + app.get("/project/yandexauth", async (req, res) => { const name = req.params.name; var templateData = { diff --git a/static/img/checkbox-check.svg b/static/img/checkbox-check.svg new file mode 100644 index 0000000..beecde4 --- /dev/null +++ b/static/img/checkbox-check.svg @@ -0,0 +1,6 @@ + diff --git a/static/img/checkbox.svg b/static/img/checkbox.svg new file mode 100644 index 0000000..060a36e --- /dev/null +++ b/static/img/checkbox.svg @@ -0,0 +1,4 @@ + diff --git a/static/img/down-input.svg b/static/img/down-input.svg new file mode 100644 index 0000000..751d710 --- /dev/null +++ b/static/img/down-input.svg @@ -0,0 +1,8 @@ + + + diff --git a/static/img/krbl-square.mp4 b/static/img/krbl-square.mp4 new file mode 100755 index 0000000..436de80 Binary files /dev/null and b/static/img/krbl-square.mp4 differ diff --git a/static/scripts/notifications.js b/static/scripts/notifications.js new file mode 100644 index 0000000..86627de --- /dev/null +++ b/static/scripts/notifications.js @@ -0,0 +1,97 @@ +if (location.pathname.match(/fullcpgrid/i) ? true : false) { + document.documentElement.style.fontSize = "32px"; + document.querySelector(".notifications").style.transform = + "translate(0.5rem, calc(-50% + 3rem))"; +} + +class Notifications { + constructor(el) { + this.el = el; + } + + createDiv(className = "") { + const el = document.createElement("div"); + el.classList.add(className); + return el; + } + addText(el, text) { + el.appendChild(document.createTextNode(text)); + } + + create( + title = "Untitled notification", + description = "", + duration = 2, + destroyOnClick = false, + clickFunction = undefined + ) { + function destroy(animate) { + if (animate) { + notiEl.classList.add("out"); + notiEl.addEventListener("animationend", () => { + notiEl.remove(); + }); + } else { + notiEl.remove(); + } + } + + const notiEl = this.createDiv("noti"); + const notiCardEl = this.createDiv("noticard"); + const glowEl = this.createDiv("notiglow"); + const borderEl = this.createDiv("notiborderglow"); + + const titleEl = this.createDiv("notititle"); + this.addText(titleEl, title); + + const descriptionEl = this.createDiv("notidesc"); + this.addText(descriptionEl, description); + + notiEl.appendChild(notiCardEl); + notiCardEl.appendChild(glowEl); + notiCardEl.appendChild(borderEl); + notiCardEl.appendChild(titleEl); + notiCardEl.appendChild(descriptionEl); + + this.el.appendChild(notiEl); + + console.log("height", notiCardEl.scrollHeight); + requestAnimationFrame(function () { + notiEl.style.height = + "calc(0.25rem + " + notiCardEl.getBoundingClientRect().height + "px)"; + }); + + notiEl.addEventListener("mousemove", (event) => { + const rect = notiCardEl.getBoundingClientRect(); + const localX = (event.clientX - rect.left) / rect.width; + const localY = (event.clientY - rect.top) / rect.height; + + console.log(localX, localY); + glowEl.style.left = localX * 100 + "%"; + glowEl.style.top = localY * 100 + "%"; + + borderEl.style.left = localX * 100 + "%"; + borderEl.style.top = localY * 100 + "%"; + }); + + if (clickFunction != undefined) { + notiEl.addEventListener("click", clickFunction); + } + + if (destroyOnClick) { + notiEl.addEventListener("click", () => destroy(true)); + } + + if (duration != 0) { + setTimeout(() => { + notiEl.classList.add("out"); + notiEl.addEventListener("animationend", () => { + notiEl.remove(); + }); + }, duration * 1000); + } + return notiEl; + } +} + +notis = new Notifications(document.querySelector(".notifications")); diff --git a/static/scripts/users-table.js b/static/scripts/users-table.js new file mode 100644 index 0000000..a9c0c3c --- /dev/null +++ b/static/scripts/users-table.js @@ -0,0 +1,36 @@ +$(document).ready(function () { + const createTable = () => { + const table = document.getElementById("usersTable"); + const tbody = table.querySelector("tbody"); + // Очищаем таблицу + tbody.innerHTML = ""; + + users.forEach((user) => { + const row = document.createElement("tr"); + row.setAttribute( + "onclick", + `location.href = '/admin/user/` + user.id + `'` + ); + + const email = document.createElement("td"); + email.textContent = user.email; + row.appendChild(email); + + const name = document.createElement("td"); + name.textContent = user.name; + row.appendChild(name); + + const phone = document.createElement("td"); + phone.textContent = user.phone; + row.appendChild(phone); + + const projects = document.createElement("td"); + projects.textContent = user.projects; + row.appendChild(projects); + + tbody.appendChild(row); + }); + }; + + createTable(); +}); diff --git a/static/styles/main.css b/static/styles/main.css index 6463a9a..14fbb16 100644 --- a/static/styles/main.css +++ b/static/styles/main.css @@ -371,6 +371,28 @@ input[type="number"] { margin-bottom: 13px; } +.form-cell-wrapper select { + width: 100%; + box-sizing: border-box; + color: rgba(0, 0, 0, 0.7); + font-weight: 400; + background: #ffffff; + border: 1px solid #d6d6d6; + outline: none; + background-repeat: no-repeat; + transition: 0.2s; + float: left; + border-radius: 5px; + height: 46px; + padding-left: 10px; + font-size: 18px; +} + +.form-cell-wrapper select:focus, +.form-cell-wrapper select:hover { + border: 1px solid rgba(0, 0, 0, 0.6); +} + .form-cell-wrapper textarea { padding-top: 35px; height: auto; @@ -792,7 +814,7 @@ header.menu-opened ul.menu li.menu-item:nth-child(7) { width: 100%; align-items: flex-start; align-content: flex-start; - gap: 26px 37px; + gap: 26px 34px; flex-wrap: wrap; } @@ -1311,6 +1333,10 @@ header.menu-opened ul.menu li.menu-item:nth-child(7) { background: #fafafa; } +.project-content .updates .section-header { + width: 370px; +} + .section-header .icon { display: flex; width: 40px; @@ -1472,6 +1498,11 @@ header.menu-opened ul.menu li.menu-item:nth-child(7) { opacity: 0.6; } +.project-info video:hover { + opacity: 1 !important; + cursor: default !important; +} + .work-status { position: relative; padding-left: 15px; @@ -1589,6 +1620,7 @@ header.menu-opened ul.menu li.menu-item:nth-child(7) { .project-payments-content { display: flex; + min-height: 210px; } circle-progress { @@ -1808,16 +1840,27 @@ li { width: calc(100% - 30px); } +.empty-span-area { + width: 100%; + height: calc(100% - 86px); + display: flex; +} + .empty-span { + font-size: 18px; width: fit-content; height: fit-content; margin: auto; + text-align: center; + padding: 0 20px; text-transform: uppercase; color: rgba(0, 0, 0, 0.6); } .upload-button { - width: 280px; + text-wrap: nowrap; + white-space: nowrap; + -webkit-text-wrap: none; color: #fff; text-align: center; font-size: 17px; @@ -1834,7 +1877,7 @@ li { align-content: center; align-items: center; gap: 8px; - padding: 0; + padding: 0 14px; cursor: pointer; } @@ -1951,8 +1994,10 @@ li { margin: 0; } -.setting-top input { - width: calc(300px - 10px); +.setting-top input, +.setting-top select { + width: 300px; + box-sizing: border-box; color: rgba(0, 0, 0, 0.7); font-weight: 400; background: #ffffff; @@ -1968,10 +2013,21 @@ li { } .setting-top input:focus, -.setting-top input:hover { +.setting-top input:hover, +.setting-top select:focus, +.setting-top select:hover { border: 1px solid rgba(0, 0, 0, 0.6); } +select { + cursor: pointer; + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; + background: url(../img/down-input.svg) no-repeat right white !important; + background-position-x: calc(100% - 14px) !important; +} + .setting-bottom { box-sizing: border-box; display: flex; @@ -2005,8 +2061,11 @@ li { font-weight: 500; line-height: 130%; letter-spacing: -0.14px; + outline: none !important; height: 32px; + border: 1px solid #000; cursor: pointer; + transition: 0.3s; } .setting-bottom button:hover { @@ -2018,6 +2077,272 @@ li { background-color: rgba(0, 0, 0, 0.7); } +.setting-bottom button.inactive, +.setting-bottom button.inactive:hover, +.setting-bottom button.inactive:active { + color: rgb(143, 143, 143); + background: rgb(242, 242, 242); + border: 1px solid rgba(143, 143, 143, 0.2); + cursor: not-allowed; +} + +.setting-bottom #deleteUser { + background: rgb(255, 69, 58); + border: 1px solid rgb(255, 69, 58); +} + +.setting-bottom #deleteUser:hover { + background: rgba(255, 69, 58, 0.85); +} + +.setting-bottom #deleteUser:active { + color: rgba(255, 255, 255, 0.85); + background: rgba(255, 69, 58, 0.7); +} + +.notifications { + width: 18rem; + height: 50%; + min-height: 400px; + position: fixed; + z-index: 2; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + justify-content: end; + margin: 0.5rem; +} + +.noti { + position: relative; + height: 0; + transition: height 300ms ease; + flex-shrink: 0; + opacity: 1; +} +.noti.out { + animation: notiOut 500ms ease forwards; +} +@keyframes notiOut { + to { + height: 0; + } +} + +.noticard { + position: absolute; + bottom: 0; + display: flex; + flex-direction: column; + isolation: isolate; + width: 18rem; + d-height: 8rem; + background: white; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 1rem; + overflow: hidden; + animation: notiCardIn 500ms ease; +} +@keyframes notiCardIn { + from { + transform: translateX(50%); + opacity: 0; + } +} +.noti.out .noticard { + animation: notiCardOut 500ms ease forwards; +} +@keyframes notiCardOut { + to { + opacity: 0; + transform: scale(0.5); + } +} +.noticard:before { + position: absolute; + content: ""; + inset: 0.0625rem; + border-radius: 0.9375rem; + background: white; + z-index: 2; +} +.noticard:after { + position: absolute; + content: ""; + width: 0.25rem; + inset: 0.65rem auto 0.65rem 0.5rem; + border-radius: 0.125rem; + background: linear-gradient(to bottom, #ffc843, #00b3e3, #ff4338); + transition: transform 300ms ease; + z-index: 4; +} + +.noticard:hover:after { + transform: translateX(0.15rem); +} + +.notititle { + padding: 0.65rem 0.5rem 0.4rem 1.25rem; + font-weight: 500; + font-size: 1.1rem; + transition: transform 300ms ease; + z-index: 5; +} +.noti:hover .notititle { + transform: translateX(0.15rem); +} +.notidesc { + color: #99999d; + padding: 0 0.5rem 0.85rem 1.25rem; + transition: transform 300ms ease; + z-index: 5; +} +.noti:hover .notidesc { + transform: translateX(0.25rem); +} + +.notiglow, +.notiborderglow { + position: absolute; + width: 20rem; + height: 20rem; + transform: translate(-50%, -50%); + background: radial-gradient( + circle closest-side at center, + white, + transparent + ); + opacity: 0; + transition: opacity 300ms ease; +} +.notiglow { + z-index: 3; +} +.notiborderglow { + z-index: 1; +} + +.noti:hover .notiglow { + opacity: 0.1; +} +.noti:hover .notiborderglow { + opacity: 0.1; +} + +.users-table { + width: 100%; + border-collapse: collapse; + overflow: hidden; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1); + border-radius: 6px; + background-color: white; +} + +.users-table th { + color: rgba(0, 0, 0, 0.8); + font-size: 20px; + padding: 14px 14px 14px 28px; + border: 0; + text-align: left; +} + +.users-table td { + color: rgba(0, 0, 0, 0.6); + font-size: 18px; + padding: 14px 18px 14px 28px; + border-bottom: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); +} + +.users-table tbody tr { + transition: 0.2s; + cursor: pointer; + background-color: white; +} + +.users-table tbody tr:hover { + filter: brightness(0.9); +} + +.setting-section .area-projects { + margin: 0; + padding-left: 0; +} + +.setting-section .project { + height: 28px; + list-style: none; + position: relative; + padding-left: 0; +} + +.setting-section .project img { + margin-left: -22px; + padding-right: 7px; +} + +.setting-section .project label { + color: rgba(0, 0, 0, 0.5); + cursor: pointer !important; +} + +.setting-section .checkbox-input:checked { + color: rgba(0, 0, 0, 0.9) !important; +} + +.setting-section .checkbox-input:hover { + filter: brightness(0.95) !important; +} + +.setting-section .checkbox-input:checked { + background: url(../img/checkbox-check.svg); +} + +.setting-section .checkbox-label:hover + .checkbox-label { + color: rgba(0, 0, 0, 0.7) !important; +} + +.setting-section .checkbox-input:checked + .checkbox-label { + color: rgba(0, 0, 0, 0.9) !important; +} + +.setting-section .checkmark { + display: inline-block; + width: 18px; + height: 18px; + background: url(../img/checkbox.svg); + margin-right: 5px; + margin-bottom: -3px; + transition: 0.2s; +} + +.setting-section .checkbox-input:hover + .checkbox-label .checkmark { + filter: brightness(0.95) !important; +} + +.setting-section .checkbox-input:checked + .checkbox-label .checkmark { + background: url(../img/checkbox-check.svg); +} + +input[type="checkbox"] { + display: none; +} + +#deleteConfirm { + background: rgb(255, 69, 58); + color: white; +} + +#deleteConfirm:hover { + background: rgba(255, 69, 58, 0.8); +} + +#deleteConfirm:active { + color: rgba(255, 255, 255, 0.85); + background: rgba(255, 69, 58, 0.7); +} + @media (max-width: 1690px) { /* при разрешении монитора до 1690 пикселей */ @@ -2054,7 +2379,8 @@ li { width: 1300px; } - .project-content .updates table { + .project-content .updates table, + .project-content .updates .section-header { width: 360px; } diff --git a/static/templates/admin/users.html b/static/templates/admin/users.html new file mode 100644 index 0000000..a393229 --- /dev/null +++ b/static/templates/admin/users.html @@ -0,0 +1,352 @@ + + + +
+ + +| Электронная почта | +Имя | +Номер телефона | +Проекты | +
|---|
|
-
-
-
-
-
- Макет сайта- |
-
|
-
-
-
-
-
- Техническое задание-docx- |
-
|
-
-
-
-
-
- Договор на разработку сайта- |
-
diff --git a/static/templates/user/settings.html b/static/templates/user/settings.html index 89a158a..ca35ebf 100644 --- a/static/templates/user/settings.html +++ b/static/templates/user/settings.html @@ -4,7 +4,7 @@
-
+
@@ -114,7 +114,7 @@
- + @@ -124,9 +124,14 @@ +
+
Отображаемое Имя
- Пожалуйста, укажите своё полное имя.
-
+ Отображаемое Имя
+ Пожалуйста, укажите своё полное имя
+
+ Используйте максимум 20 символов.
+
+ Используйте максимум 20 символов.
-
- Электронная почта
- Пожалуйста, укажите почту для авторизации.
-
+ Пожалуйста, укажите почту для авторизации
+
Используйте только свою актуальную почту.
-
+
Номер телефона
- Пожалуйста, укажите свой номер телефона.
-
+ Пожалуйста, укажите свой номер телефона
+
Необязательно, но определенно рекомендовано.
-
+
+ Пароль
+ Если хотите изменить пароль, укажите новый ниже
+
+ Используйте минимум 5 символов.
+