From 308e83492615cf45289ef39fde841229128f1317 Mon Sep 17 00:00:00 2001 From: Ksu Date: Wed, 16 Oct 2024 20:46:01 +0300 Subject: [PATCH 01/35] header fixes --- README.md | 2 +- public/components/Header/Header.css | 32 ++++++++++++++--------------- public/components/Login/Login.css | 8 ++++++-- public/components/Nav/Nav.css | 17 +++++++++++---- public/index.css | 9 ++++---- 5 files changed, 40 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 77ee519..629b187 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Frontend проекта "KudaGo" Команда 7 * [Backend](https://github.com/go-park-mail-ru/2024_2_Team7) # Проект -* http://vyhodnoy.online/ +* [vyhodnoy.ru]http://37.139.40.252/ # figma * https://www.figma.com/design/B9I0SPwTjYkMcqq6MwO2jW/kudaGo?node-id=0-1&t=DndAvQ4zTz4isemp-1 diff --git a/public/components/Header/Header.css b/public/components/Header/Header.css index 754eacc..a8ce2d1 100644 --- a/public/components/Header/Header.css +++ b/public/components/Header/Header.css @@ -1,18 +1,10 @@ header { background-color: white; - /* Цвет фона */ color: black; - /* Цвет текста */ display: flex; - /* Используем flexbox для выравнивания */ justify-content: space-between; - /* Распределяем пространство между элементами */ align-items: center; - /* Выравниваем элементы по центру */ padding: 10px 20px; - /* Отступы */ - display: grid; - grid-template-columns: 200px 1fr 1fr; gap: 20px; } @@ -22,21 +14,28 @@ header { padding-left: 50px; text-decoration: none; color: black; -} - -.buttons { - display: flex; - justify-content: end; - align-items: center; - gap: 20px; + margin-left: 30px; + flex-basis: 200px; } .searchbar { + padding: 10px; height: 40px; border-radius: 10px; font-size: 14px; background-color: lightgray; border: 1px solid #ccc; + flex-grow: 1; + margin: 0 20px; + min-width: 200px; +} + +.buttons { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 20px; + flex-basis: 400px; } button { @@ -58,10 +57,11 @@ button { .btnRegister { color: white; background-color: black; + margin-right: 85px; } .avatar { width: 50px; height: 50px; object-fit: fill; -} \ No newline at end of file +} diff --git a/public/components/Login/Login.css b/public/components/Login/Login.css index c70d6f3..d44dfb7 100644 --- a/public/components/Login/Login.css +++ b/public/components/Login/Login.css @@ -14,7 +14,6 @@ form { form label { font-size: 18px; - font-weight: bold; margin-bottom: 10px; color: black; display: block; @@ -27,12 +26,17 @@ form input { padding: 10px; margin-bottom: 20px; font-size: 16px; - border: 1px solid #ccc; + border: 2px solid #ccc; border-radius: 5px; background-color: #fff; box-sizing: border-box; } +form input:focus { + border-color: gray; + outline: none; +} + form button { background-color: black; color: white; diff --git a/public/components/Nav/Nav.css b/public/components/Nav/Nav.css index 013627c..59a5a17 100644 --- a/public/components/Nav/Nav.css +++ b/public/components/Nav/Nav.css @@ -4,14 +4,22 @@ nav { height: 70px; letter-spacing: 1px; font-size: 18px; - padding-left: 50px; - padding-right: 50px; + padding: 0 100px 0 60px; + display: flex; + justify-content: center; + align-items: center; } nav ul { list-style-type: none; display: flex; - justify-content: space-around; + justify-content: space-between; + width: 100%; +} + +nav ul li { + display: flex; + align-items: center; } nav ul li a { @@ -19,4 +27,5 @@ nav ul li a { text-decoration: none; font-weight: bold; border-radius: 4px; -} \ No newline at end of file + padding: 10px; +} diff --git a/public/index.css b/public/index.css index 5a650f0..0ed083e 100644 --- a/public/index.css +++ b/public/index.css @@ -3,7 +3,8 @@ @import "components/Nav/Nav.css"; @import "components/Footer/Footer.css"; body { - font-family: 'Times New Roman', Times, serif; + font-family: Arial, sans-serif; + font-weight: bold; margin: 0; } @@ -76,12 +77,10 @@ body { border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); padding: 20px; - transition: transform 0.3s ease-in-out; + margin: 20px; } -.feed-element:hover { - transform: scale(1.05); -} + .feed-element img { width: 100%; From 7214ebf8e600d6746cfe0633a489068a97c33ee0 Mon Sep 17 00:00:00 2001 From: Ksu Date: Wed, 16 Oct 2024 20:50:45 +0300 Subject: [PATCH 02/35] header fixes --- public/components/Nav/Nav.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/components/Nav/Nav.css b/public/components/Nav/Nav.css index 59a5a17..6522fa6 100644 --- a/public/components/Nav/Nav.css +++ b/public/components/Nav/Nav.css @@ -4,7 +4,7 @@ nav { height: 70px; letter-spacing: 1px; font-size: 18px; - padding: 0 100px 0 60px; + padding: 0 100px 0 50px; display: flex; justify-content: center; align-items: center; From 4862209f352293fc400dd4c8311e82f8e17acc74 Mon Sep 17 00:00:00 2001 From: Achpochmak <44942485+Achpochmak@users.noreply.github.com> Date: Wed, 16 Oct 2024 20:53:04 +0300 Subject: [PATCH 03/35] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae19952..e1fdb43 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Frontend проекта "KudaGo" Команда 7 * [Backend](https://github.com/go-park-mail-ru/2024_2_Team7) # Проект -* [vyhodnoy.ru]http://37.139.40.252/ +* [vyhodnoy.ru](http://37.139.40.252/) # figma * https://www.figma.com/design/B9I0SPwTjYkMcqq6MwO2jW/kudaGo?node-id=0-1&t=DndAvQ4zTz4isemp-1 From f6f9f0b1ec29ea54bf39c37478e3266a9f7af107 Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Sun, 27 Oct 2024 12:29:38 +0300 Subject: [PATCH 04/35] fixed feed rendering --- public/components/Feed/Feed.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/public/components/Feed/Feed.js b/public/components/Feed/Feed.js index ee5dc21..725f94d 100644 --- a/public/components/Feed/Feed.js +++ b/public/components/Feed/Feed.js @@ -86,9 +86,12 @@ export class Feed { * @param {string} description - The description of the event. * @param {string} image - The image URL of the event. */ - Object.entries(feed).forEach(([key, { description, image }]) => { - const feedElement = new FeedElement(key, description, `${endpoint}${image}`).renderTemplate(); + console.log(feed.events); + Object.entries(feed.events).forEach( (elem) => { + const {id, description, image} = elem[1]; + const feedElement = new FeedElement(id, description, `${endpoint}${image}`).renderTemplate(); feedContent.appendChild(feedElement); + //console.log(elem[1]); }); } else { From ee6c46f4ad2d7ebc6bb2bde9e1cba2dbe6de1f99 Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Sun, 27 Oct 2024 12:31:31 +0300 Subject: [PATCH 05/35] fixed problems with index html paths --- public/index.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/index.html b/public/index.html index d985863..9d8a56e 100644 --- a/public/index.html +++ b/public/index.html @@ -5,16 +5,16 @@ Выходной - - + +
- - - - + + + + From ac4ced6d6ad62f69c6dffdf10d40e2292e410d8b Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Wed, 30 Oct 2024 11:27:05 +0300 Subject: [PATCH 06/35] add: event page --- .../EventContentPage/EventContentPage.css | 67 +++++++++ .../EventContentPage/EventContentPage.js | 134 ++++++++++++++++++ public/index.css | 1 + public/index.js | 15 +- 4 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 public/components/EventContentPage/EventContentPage.css create mode 100644 public/components/EventContentPage/EventContentPage.js diff --git a/public/components/EventContentPage/EventContentPage.css b/public/components/EventContentPage/EventContentPage.css new file mode 100644 index 0000000..b1af074 --- /dev/null +++ b/public/components/EventContentPage/EventContentPage.css @@ -0,0 +1,67 @@ +.eventPage { + display: flex; + border: 1px solid #ccc; + padding: 10px; + margin: 10px; +} + +.event__leftPart { + flex: 1; + display: flex; + flex-direction: column; + padding-right: 10px; +} + +.event__rightPart { + display: block; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + +} + +.event__leftPart__title { + font-size: 20px; + font-weight: bold; +} + +.event__date { + margin: 5px 0; +} + +.event_actionsDiv { + display:inline +} + +.event_tagsDiv { + display:inline +} + +.image { + max-width: 100%; + height: auto; +} + +.buttonDelete { + background-color: red; + color: white; + padding: 10px 20px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s; +} + +.buttonDelete { + background-color: red; + color: white; + padding: 10px 20px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s; +} \ No newline at end of file diff --git a/public/components/EventContentPage/EventContentPage.js b/public/components/EventContentPage/EventContentPage.js new file mode 100644 index 0000000..9cb8c43 --- /dev/null +++ b/public/components/EventContentPage/EventContentPage.js @@ -0,0 +1,134 @@ +import { FrontendAPI } from "../../modules/FrontendAPI.js"; + +export class EventContentPage { + constructor(eventId) { + this.contentBody = document.createElement('div'); + this.contentBody.className = 'eventPage'; + + this.eventId = eventId; + } + config = { + author: { + text: '', + tag: 'label', + className: '', + }, + dateStart: { + text: '', + tag: 'label', + className: '', + }, + dateEnd: { + text: '', + tag: 'label', + className: '', + }, + title: { + text: '', + tag: 'label', + className: '', + }, + description: { + text: '', + tag: 'label', + className: '', + }, + tag: { + text: '', + tag: 'label', + className: '', + }, + image: { + text: '', + tag: 'label', + className: '', + src: '', + }, + + }; + + renderEvent(event){ + console.log(event); + console.log(event.title); + + const leftDiv = document.createElement('div'); + leftDiv.className = 'event__leftPart'; + + const title = document.createElement('div'); + title.className = 'event__leftPart__title '; + title.textContent = event.title; + + const image = document.createElement('img'); + image.className = 'image'; + image.src = this.config.image.src; + image.onerror = function() { + this.src = "/static/images/placeholder.png"; + this.style.objectFit = 'fill'; + }; + + const descr = document.createElement('div'); + //descr.className = 'title'; + descr.textContent = event.description; + + leftDiv.appendChild(title); + leftDiv.appendChild(image); + leftDiv.appendChild(descr); + + const rightDiv = document.createElement('div'); + rightDiv.className = 'event__rightPart'; + + const actionsDiv = document.createElement('div'); + rightDiv.className = 'event_actionsDiv'; + + const btnDeleteEvent = document.createElement('button'); + btnDeleteEvent.className = 'buttonDelete'; + btnDeleteEvent.textContent = 'Delete'; + + actionsDiv.appendChild(btnDeleteEvent); + + const tagsDiv = document.createElement('div'); + tagsDiv.className = 'event_tagsDiv'; + tagsDiv.textContent = event.tag[0]; + + const startDate = document.createElement('div'); + startDate.className = 'event__date'; + startDate.textContent = 'Date start: ' + event.date_start; // Укажите вашу дату начала + + const endDate = document.createElement('div'); + endDate.className = 'event__date'; + endDate.textContent = 'Date end: ' + event.date_end; // Укажите вашу дату окончания + + rightDiv.appendChild(actionsDiv); + rightDiv.appendChild(tagsDiv); + rightDiv.appendChild(startDate); + rightDiv.appendChild(endDate); + + // Добавляем левую и правую части в основной контейнер + this.contentBody.appendChild(leftDiv); + this.contentBody.appendChild(rightDiv); + + } + async renderTemplate(id) { + console.log(id); + const api = new FrontendAPI(); + const path = '/events/'+id.toString(); + const request = {headers: {}}; + try { + const response = await api.get(path, request); + + const event = await response.json(); + console.log(event); + console.log("here"); + /** render event */ + this.renderEvent(event); + //this.leftPart = document.createElement('leftPart'); + //this.rightPart = document.createElement('rightPart'); + + } catch (error) { + console.log(error); + console.log("ERROR HERE"); + /* some more useful error handling */ + } + return this.contentBody; + } +} \ No newline at end of file diff --git a/public/index.css b/public/index.css index 0ed083e..e0a8b88 100644 --- a/public/index.css +++ b/public/index.css @@ -2,6 +2,7 @@ @import "components/Header/Header.css"; @import "components/Nav/Nav.css"; @import "components/Footer/Footer.css"; +@import "components/EventContentPage/EventContentPage.css"; body { font-family: Arial, sans-serif; font-weight: bold; diff --git a/public/index.js b/public/index.js index 3fff1c2..b18583c 100644 --- a/public/index.js +++ b/public/index.js @@ -10,6 +10,7 @@ import { RegisterForm } from "./components/Register/Register.js"; import { Header } from "./components/Header/Header.js"; import { Nav } from "./components/Nav/Nav.js"; import { Feed } from "./components/Feed/Feed.js"; +import { EventContentPage } from "./components/EventContentPage/EventContentPage.js"; import { Footer } from "./components/Footer/Footer.js"; import { checkSession } from './modules/session.js'; import { handleRegisterSubmit, handleRegisterCheck } from './modules/registerForm.js'; @@ -161,6 +162,11 @@ const routes = { let feed = await new Feed().renderFeed(); newsFeed.appendChild(feed); }, + '/events/:id': async(id) => { + newsFeed.innerHTML = ''; // Clear the modal window content + let eventPage = await new EventContentPage('event').renderTemplate(id); + newsFeed.appendChild(eventPage); + }, }; /** @@ -207,11 +213,18 @@ if (currentPath === '/login' || currentPath === '/signup') { */ route(); } -} else if (currentPath === '/events' || currentPath === "/") { +} else if (currentPath === '/events' || currentPath === '/') { /** * Call the events route function */ routes['/events'](); +} else if (/\/events\/\d+/.test(currentPath)) { + /** + * Call the events route function + */ + const id = currentPath.split('/')[2]; + routes['/events/:id'](id); // Вызываем обработчик с id + } else { /** * Call the default route function From 37a8d621993f9a65b7c1ede0ea71c31ad8dd360b Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Wed, 30 Oct 2024 11:27:53 +0300 Subject: [PATCH 07/35] ADD: fetch module --- public/modules/FrontendAPI.js | 73 +++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 public/modules/FrontendAPI.js diff --git a/public/modules/FrontendAPI.js b/public/modules/FrontendAPI.js new file mode 100644 index 0000000..aa971cf --- /dev/null +++ b/public/modules/FrontendAPI.js @@ -0,0 +1,73 @@ +import { endpoint } from "../config.js"; + +export class FrontendAPI { + /* * + {path, headers = {}, needCredentials = false, id = null} + object like request + */ + get(path, request) { + request['method'] = 'GET'; + return this._commonFetchRequest(path, request); + /* + return this._commonFetchRequest({ + method: 'GET', + path, + headers, + needCredentials, + id, + });*/ + } + + _removeNullUndefined(obj) { + for (const key in obj) { + if (obj[key] === null || obj[key] === undefined) { + delete obj[key]; + } + } + return obj; + } + /** + * { method, path, headers = {}, needCredentials = false, body = null, id = null} + */ + async _commonFetchRequest(path, request) { + /* + const id = request.id; + const url = endpoint + request.path + (id ? `/${id}` : ''); + console.log(url); + + const pattern = this._removeNullUndefined(arguments[0]); + console.log(pattern); + const objRequest = {}; + + for (const element in pattern) { + if (element !== null && element !== undefined) { + const name = Object.keys({element})[0]; + objRequest[name] = element; + } + } + + console.log(objRequest); + */ + const url = endpoint + path; + + /* { + method: request.method, + headers: request.headers, + }*/ + + const response = await fetch(url, request); + /*body: JSON.stringify(body), + credentials: needCredentials ? 'include' : '', */ + //const clonedResponse = response.clone(); + + if (!response.ok) { + const errorMessage = await response.text(); + throw { + error: new Error(`Error ${response.status}: ${errorMessage}`), + status: response.status, + }; + } + //const resp = await response.json(); + return response; + } +} \ No newline at end of file From 2cd9f15f2f8d720ceecda767246c4a3d8c6d4671 Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Sun, 3 Nov 2024 12:02:34 +0300 Subject: [PATCH 08/35] ADD: event edit and create form --- package.json | 3 +- .../EventCreateForm/EventCreateForm.css | 0 .../EventCreateForm/EventCreateForm.hbs | 7 + .../EventCreateForm/EventCreateForm.js | 217 +++++++++++++++++ .../EventCreateForm.precompiled.js | 64 +++++ .../EventEditForm/EventEditForm.css | 0 .../EventEditForm/EventEditForm.hbs | 0 .../components/EventEditForm/EventEditForm.js | 225 ++++++++++++++++++ 8 files changed, 515 insertions(+), 1 deletion(-) create mode 100644 public/components/EventCreateForm/EventCreateForm.css create mode 100644 public/components/EventCreateForm/EventCreateForm.hbs create mode 100644 public/components/EventCreateForm/EventCreateForm.js create mode 100644 public/components/EventCreateForm/EventCreateForm.precompiled.js create mode 100644 public/components/EventEditForm/EventEditForm.css create mode 100644 public/components/EventEditForm/EventEditForm.hbs create mode 100644 public/components/EventEditForm/EventEditForm.js diff --git a/package.json b/package.json index 4955674..62b0252 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "lint": "npx eslint", - "prepare": "husky && husky install" + "prepare": "husky && husky install", + "compile:templates": "handlebars public/components/EventCreateForm/EventCreateForm.hbs -f public/components/EventCreateForm/EventCreateForm.precompiled.js" }, "lint-staged": { "*.{js, jsx}": [ diff --git a/public/components/EventCreateForm/EventCreateForm.css b/public/components/EventCreateForm/EventCreateForm.css new file mode 100644 index 0000000..e69de29 diff --git a/public/components/EventCreateForm/EventCreateForm.hbs b/public/components/EventCreateForm/EventCreateForm.hbs new file mode 100644 index 0000000..640af3c --- /dev/null +++ b/public/components/EventCreateForm/EventCreateForm.hbs @@ -0,0 +1,7 @@ +{{#each items}} + {{#if this.needPlaceholder}} + <{{this.tag}} class="{{this.className}}" id = "{{this.key}}" placeholder = "{{this.text}}" type="{{this.type}}" data-section="{{this.key}}"> + {{else}} + <{{this.tag}} class="{{this.className}}" id = "{{this.key}}" data-section="{{this.key}}">{{this.text}} + {{/if}} +{{/each}} \ No newline at end of file diff --git a/public/components/EventCreateForm/EventCreateForm.js b/public/components/EventCreateForm/EventCreateForm.js new file mode 100644 index 0000000..96e44bd --- /dev/null +++ b/public/components/EventCreateForm/EventCreateForm.js @@ -0,0 +1,217 @@ +/** + * LoginForm class + */ +export class EventCreateForm { + + constructor(formId) { + this.form = document.createElement('form'); + this.form.id = formId; + } + + config = { + eventServerError: { + text: 'очень много текста на неизвестном древнем языке', + tag: 'label', + className: 'error_text', + type: '', + }, + + eventAddLabel: { + + text: 'Создать мероприятие', + + tag: 'label', + + className: '', + + type: '', + }, + + eventNameEntry: { + /** + * Text + * @type {string} + */ + text: 'Название мероприятия', + /** + * Tag type + * @type {string} + */ + tag: 'input', + /** + * Type + * @type {string} + */ + type: 'text', + /** + * Class name + * @type {string} + */ + className: '', + }, + + eventNameError: { + text: '', + tag: 'label', + className: 'error_text', + type: '', + }, + eventDescriptionEntry: { + + text: 'Описание мероприятия', + tag: 'input', + + type: '', + + className: '', + }, + eventDescriptionError: { + /** + * Error text + * @type {string} + */ + text: '', + /** + * Tag type + * @type {string} + */ + tag: 'label', + /** + * Class name + * @type {string} + */ + className: 'error_text', + /** + * Type + * @type {string} + */ + type: '', + }, + + eventTagsEntry: { + + text: 'Тэги (не более 3 штуки)', + + tag: 'input', + + type: '', + + className: '', + }, + + eventTagsError: { + + text: '', + + tag: 'label', + + className: 'error_text', + + type: '', + }, + + eventBeginEntry: { + + text: 'Время начала мероприятия', + + tag: 'input', + + type: '', + + className: '', + }, + + eventBeginError: { + + text: '', + + tag: 'label', + + className: 'error_text', + + type: '', + }, + + eventEndEntry: { + /** + * Text + * @type {string} + */ + text: 'Время окончания мероприятия', + /** + * Tag type + * @type {string} + */ + tag: 'input', + /** + * Type + * @type {string} + */ + type: '', + /** + * Class name + * @type {string} + */ + className: '', + }, + + eventEndError: { + /** + * Error text + * @type {string} + */ + text: '', + /** + * Tag type + * @type {string} + */ + tag: 'label', + /** + * Class name + * @type {string} + */ + className: 'error_text', + /** + * Type + * @type {string} + */ + type: '', + }, + + eventSubmitBtn: { + + text: 'Создать', + + tag: 'button', + + type: 'submit', + + className: '', + }, + }; + + /** + * Renders the form template + * @returns {HTMLFormElement} The rendered form + */ + renderTemplate() { + + const template = Handlebars.templates['EventCreateForm.hbs']; + console.log( template ); + const config = this.config; + let itemsArray = Object.entries(config); + let items = itemsArray.map(([key, {tag, text, className, type}], index) => { + let needPlaceholder = (tag === 'input'); + return {key, tag, text, className, type, needPlaceholder}; + }); + console.log(items); + + this.form.innerHTML += template({items}); + + //const createBtn = document.getElementById('createBtn'); + //createBtn.addEventListener(); + + return this.form; + } + } + \ No newline at end of file diff --git a/public/components/EventCreateForm/EventCreateForm.precompiled.js b/public/components/EventCreateForm/EventCreateForm.precompiled.js new file mode 100644 index 0000000..1bfa167 --- /dev/null +++ b/public/components/EventCreateForm/EventCreateForm.precompiled.js @@ -0,0 +1,64 @@ +(function() { + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; +templates['EventCreateForm.hbs'] = template({"1":function(container,depth0,helpers,partials,data) { + var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return ((stack1 = lookupProperty(helpers,"if").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"needPlaceholder") : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(4, data, 0),"data":data,"loc":{"start":{"line":2,"column":4},"end":{"line":6,"column":11}}})) != null ? stack1 : ""); +},"2":function(container,depth0,helpers,partials,data) { + var alias1=container.lambda, alias2=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return " <" + + alias2(alias1((depth0 != null ? lookupProperty(depth0,"tag") : depth0), depth0)) + + " class=\"" + + alias2(alias1((depth0 != null ? lookupProperty(depth0,"className") : depth0), depth0)) + + "\" id = \"" + + alias2(alias1((depth0 != null ? lookupProperty(depth0,"key") : depth0), depth0)) + + "\" placeholder = \"" + + alias2(alias1((depth0 != null ? lookupProperty(depth0,"text") : depth0), depth0)) + + "\" type=\"" + + alias2(alias1((depth0 != null ? lookupProperty(depth0,"type") : depth0), depth0)) + + "\" data-section=\"" + + alias2(alias1((depth0 != null ? lookupProperty(depth0,"key") : depth0), depth0)) + + "\">\n"; +},"4":function(container,depth0,helpers,partials,data) { + var alias1=container.lambda, alias2=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return " <" + + alias2(alias1((depth0 != null ? lookupProperty(depth0,"tag") : depth0), depth0)) + + " class=\"" + + alias2(alias1((depth0 != null ? lookupProperty(depth0,"className") : depth0), depth0)) + + "\" id = \"" + + alias2(alias1((depth0 != null ? lookupProperty(depth0,"key") : depth0), depth0)) + + "\" >" + + alias2(alias1((depth0 != null ? lookupProperty(depth0,"text") : depth0), depth0)) + + "\n"; +},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) { + var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return ((stack1 = lookupProperty(helpers,"each").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"items") : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":9}}})) != null ? stack1 : ""); +},"useData":true}); +})(); \ No newline at end of file diff --git a/public/components/EventEditForm/EventEditForm.css b/public/components/EventEditForm/EventEditForm.css new file mode 100644 index 0000000..e69de29 diff --git a/public/components/EventEditForm/EventEditForm.hbs b/public/components/EventEditForm/EventEditForm.hbs new file mode 100644 index 0000000..e69de29 diff --git a/public/components/EventEditForm/EventEditForm.js b/public/components/EventEditForm/EventEditForm.js new file mode 100644 index 0000000..306a1d0 --- /dev/null +++ b/public/components/EventEditForm/EventEditForm.js @@ -0,0 +1,225 @@ +/** + * LoginForm class + */ +export class LoginForm { + /** + * Creates a new LoginForm instance + * @param {string} formId - The ID of the form + */ + constructor(formId) { + /** + * The form element + * @type {HTMLFormElement} + */ + this.form = document.createElement('form'); + this.form.id = formId; + } + + /** + * Configuration object for the form + * @type {Object} + */ + config = { + /** + * Login server error configuration + * @type {Object} + */ + loginServerError: { + /** + * Error text + * @type {string} + */ + text: '', + /** + * Tag type + * @type {string} + */ + tag: 'label', + /** + * Class name + * @type {string} + */ + className: 'error_text', + /** + * Type + * @type {string} + */ + type: '', + }, + /** + * Login label configuration + * @type {Object} + */ + loginLabel: { + /** + * Text + * @type {string} + */ + text: 'Вход', + /** + * Tag type + * @type {string} + */ + tag: 'label', + /** + * Class name + * @type {string} + */ + className: '', + /** + * Type + * @type {string} + */ + type: '', + }, + /** + * Login username entry configuration + * @type {Object} + */ + loginUsernameEntry: { + /** + * Text + * @type {string} + */ + text: 'Имя пользователя', + /** + * Tag type + * @type {string} + */ + tag: 'input', + /** + * Type + * @type {string} + */ + type: 'text', + /** + * Class name + * @type {string} + */ + className: '', + }, + /** + * Login username error configuration + * @type {Object} + */ + loginUsernameError: { + /** + * Error text + * @type {string} + */ + text: '', + /** + * Tag type + * @type {string} + */ + tag: 'label', + /** + * Class name + * @type {string} + */ + className: 'error_text', + /** + * Type + * @type {string} + */ + type: '', + }, + /** + * Login password entry configuration + * @type {Object} + */ + loginPasswordEntry: { + /** + * Text + * @type {string} + */ + text: 'Пароль', + /** + * Tag type + * @type {string} + */ + tag: 'input', + /** + * Type + * @type {string} + */ + type: 'password', + /** + * Class name + * @type {string} + */ + className: '', + }, + /** + * Login password error configuration + * @type {Object} + */ + loginPasswordError: { + /** + * Error text + * @type {string} + */ + text: '', + /** + * Tag type + * @type {string} + */ + tag: 'label', + /** + * Class name + * @type {string} + */ + className: 'error_text', + /** + * Type + * @type {string} + */ + type: '', + }, + /** + * Login submit button configuration + * @type {Object} + */ + loginSubmitBtn: { + /** + * Text + * @type {string} + */ + text: 'Войти', + /** + * Tag type + * @type {string} + */ + tag: 'button', + /** + * Type + * @type {string} + */ + type: 'submit', + /** + * Class name + * @type {string} + */ + className: '', + }, + }; + + /** + * Renders the form template + * @returns {HTMLFormElement} The rendered form + */ + renderTemplate() { + const template = Handlebars.templates['EventEditForm.hbs']; + const config = this.config; + let itemss = Object.entries(config); + let items = itemss.map(([key, {tag, text, className, type}], index) => { + let needPlaceholder = (tag === 'input'); + return {key, tag, text, className, type, needPlaceholder}; + }); + + this.form.innerHTML += template({items}); + + return this.form; + } + } + \ No newline at end of file From 21daae780a2ae8225357197a7fbc9f44c61d25f2 Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Sun, 3 Nov 2024 12:03:18 +0300 Subject: [PATCH 09/35] ADD: user event page --- .../UserEventsPage/UserEventsPage.css | 30 +++++++ .../UserEventsPage/UserEventsPage.js | 82 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 public/components/UserEventsPage/UserEventsPage.css create mode 100644 public/components/UserEventsPage/UserEventsPage.js diff --git a/public/components/UserEventsPage/UserEventsPage.css b/public/components/UserEventsPage/UserEventsPage.css new file mode 100644 index 0000000..5aa1eb3 --- /dev/null +++ b/public/components/UserEventsPage/UserEventsPage.css @@ -0,0 +1,30 @@ +.userEventsManager { + margin: calc(var(--group-padding, 0px)* -1); + border-radius: inherit; + overflow: hidden; + isolation: isolate; +} + +.actions { + position: relative; + border-top-left-radius: inherit; + border-top-right-radius: inherit; + text-align: center; +} + +.createEvent { + background: white; + border: 2px solid #008EB0; + border-color: #008EB0; + color:#008EB0; + +} + +.createEvent:active { + background: #008EB0; + border: 2px solid #008EB0; + border-color: #008EB0; + color:white; + +} + diff --git a/public/components/UserEventsPage/UserEventsPage.js b/public/components/UserEventsPage/UserEventsPage.js new file mode 100644 index 0000000..8c1910c --- /dev/null +++ b/public/components/UserEventsPage/UserEventsPage.js @@ -0,0 +1,82 @@ +import { api } from "../../modules/FrontendAPI.js"; +import { navigate } from "../../modules/router.js"; +import { FeedElement } from "../FeedElement/FeedElement.js" +import { EventCreateForm } from "../EventCreateForm/EventCreateForm.js" + +export class UserEventsPage { + constructor(eventId) { + this.contentBody = document.createElement('div'); + this.contentBody.className = 'userEventManager'; + + this.eventId = eventId; + } + config = { + createBtn: { + }, + tag: { + text: '', + tag: 'label', + className: 'actions', + }, + image: { + text: '', + tag: 'label', + className: '', + src: '', + }, + + }; + + _formUsersEvents(divToAppend) { + + } + + renderEvent(event){ + + const createEventDiv = document.createElement('div'); + createEventDiv.className = 'actions'; + + const btnCreate = document.createElement('button'); + btnCreate.className = 'createEvent'; + btnCreate.textContent = 'Добавить мероприятие'; + + btnCreate.addEventListener('click', (event) => { + event.preventDefault(); + const path = '/add_event'; + navigate(path);}) + + createEventDiv.appendChild(btnCreate); + + const userEventsDiv = document.createElement('div'); + this._formUsersEvents(userEventsDiv); + + this.contentBody.appendChild(createEventDiv); + this.contentBody.appendChild(userEventsDiv); + + } + async renderTemplate(id) { + console.log(id); + //const path = '/events/'+id.toString(); + //const request = {headers: {}}; + /*try { + const response = await api.get(path, request); + + const event = await response.json(); + console.log(event); + console.log("here"); + + this.renderEvent(event); + //this.leftPart = document.createElement('leftPart'); + //this.rightPart = document.createElement('rightPart'); + + } catch (error) { + console.log(error); + console.log("ERROR HERE"); + + }*/ + const event = {}; + this.renderEvent(event); + console.log(this.contentBody); + return this.contentBody; + } +} \ No newline at end of file From 2b034e1931451731e8f77b4531f748580805a3bb Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Sun, 3 Nov 2024 12:08:25 +0300 Subject: [PATCH 10/35] ADD: module to handle actions with events --- public/modules/handleEventsActions.js | 180 ++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 public/modules/handleEventsActions.js diff --git a/public/modules/handleEventsActions.js b/public/modules/handleEventsActions.js new file mode 100644 index 0000000..b0ab553 --- /dev/null +++ b/public/modules/handleEventsActions.js @@ -0,0 +1,180 @@ +/** + * Registration module. + * + * This module handles the registration functionality, including form validation and backend requests. + * + * @module registration + */ +/** + * Import form validation functions from the FormValidation.js file + * @import {function} isValidUsername - Checks if a username is valid + * @import {function} isValidPassword - Checks if a password is valid + * @import {function} isValidEmail - Checks if an email is valid + * @import {function} removeDangerous - Removes dangerous characters from a string + */ +import { isValidUsername, isValidPassword, isValidEmail, removeDangerous } from './FormValidation.js'; +/** + * Import the endpoint configuration from the config.js file + * @import {string} endpoint - The API endpoint URL + */ +import { endpoint } from "../../config.js" + +import { api } from './FrontendAPI.js'; +/** + * Error message for empty fields. + * @constant {string} + */ +const EMPTY_FIELD = 'Это обязательное поле'; + +/** + * Error message for invalid usernames. + * @constant {string} + */ +const INCORRECT_USERNAME = 'Логин может состоять из латинских букв, цифр и знаков _ и быть в длину не более 15 символов'; + +/** + * Error message for invalid passwords. + * @constant {string} + */ +const INCORRECT_PASSWORD = 'Пароль должен состоят из букв и цифр'; + +/** + * Error message for invalid emails. + * @constant {string} + */ +const INCORRECT_EMAIL = 'Адрес email должен содержать несколько символов до знака @, один символ @, несколько символов после @, точка, несколько знаков послe точки'; + +/** + * Handles the registration form on submission. + * + * This function validates the form data, sends a request to the backend, and handles the response. + * + * @async + * @function handleRegisterSubmit + * @param {Event} event - The form submission event. + * @param {function} setUserLoggedIn - A function to set the user's logged-in state. + * @param {function} navigate - A function to navigate to a different page. + */ +export async function handleCreateEventSubmit(event, pageToCome, navigate) { + event.preventDefault(); + console.log('click'); + // Get form data + const title = removeDangerous(document.getElementById('eventNameEntry').value); + const description = removeDangerous(document.getElementById('eventDescriptionEntry').value); + const tags = removeDangerous(document.getElementById('eventTagsEntry').value); + const dateStart = removeDangerous(document.getElementById('eventBeginEntry').value); + const dateEnd = removeDangerous(document.getElementById('eventEndEntry').value); + console.log(title, description, tags, dateStart, dateEnd); + /* + + // Clear error messages + document.getElementById('registerUsernameError').innerText = ''; + document.getElementById('registerPasswordError').innerText = ''; + document.getElementById('registerEmailError').innerText = ''; + document.getElementById('registerServerError').innerText = ''; + + // Get form data + const username = removeDangerous(document.getElementById('registerUsernameEntry').value); + const email = removeDangerous(document.getElementById('registerEmailEntry').value); + const password = removeDangerous(document.getElementById('registerPasswordEntry').value); + + // Initialize validation flag + let isValid = true; + + // Validate form data + if (!username) { + document.getElementById('registerUsernameError').innerText = EMPTY_FIELD; + isValid = false; + } + + if (!isValidUsername(username)) { + document.getElementById('registerUsernameError').innerText = INCORRECT_USERNAME; + isValid = false; + } + + if (!isValidEmail(email)) { + document.getElementById('registerEmailError').innerText = INCORRECT_EMAIL; + isValid = false; + } + + if (!isValidPassword(password)) { + document.getElementById('registerPasswordError').innerText = INCORRECT_PASSWORD; + isValid = false; + } + + // If form data is invalid, exit function + if (!isValid) { + return; + } + */ + try { + // Send request to backend + const body = { title, description, tags, dateStart, dateEnd }; + const request = { + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify(body), + }; + const path = '/events'; + const response = await api.post(path, request); + // If response is not OK, throw error + if (!response.ok) { + throw new Error(data.message); + } + const data = await response.json(); + if (data.code) { + throw new Error(data.message); + } + // Navigate to page + navigate(pageToCome); + + } catch (error) { + // Display error message if registration fails + document.getElementById('eventServerError').innerText = error; + } + //navigate(pageToCome); //debug +} + +/** + * Handles the registration form on keyup input validation. + * + * This function dynamically checks the input fields for validity and displays error messages accordingly. + * + * @function handleRegisterCheck + * @param {Event} event - The input event. + */ +export function handleCreateEventCheck(event) { + const target = event.target; + const id = target.id; + + if (id === 'registerUsernameEntry') { + const username = removeDangerous(target.value); + if (!username) { + document.getElementById('registerUsernameError').innerText = EMPTY_FIELD; + } else if (!isValidUsername(username)) { + document.getElementById('registerUsernameError').innerText = INCORRECT_USERNAME; + } else { + document.getElementById('registerUsernameError').innerText = ''; + } + } else if (id === 'registerEmailEntry') { + const email = removeDangerous(target.value); + if (!email) { + document.getElementById('registerEmailError').innerText = EMPTY_FIELD; + } else if (!isValidEmail(email)) { + document.getElementById('registerEmailError').innerText = INCORRECT_EMAIL; + } else { + document.getElementById('registerEmailError').innerText = ''; + } + } else if (id === 'registerPasswordEntry') { + const password = removeDangerous(target.value); + if (!password) { + document.getElementById('registerPasswordError').innerText = EMPTY_FIELD; + } else if (!isValidPassword(password)) { + document.getElementById('registerPasswordError').innerText = INCORRECT_PASSWORD; + } else { + document.getElementById('registerPasswordError').innerText = ''; + } + } +} From 1b1b1309f2a356bbd27bfbf92ba137c0b300edf1 Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Sun, 3 Nov 2024 12:09:01 +0300 Subject: [PATCH 11/35] started to do router --- public/modules/router.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 public/modules/router.js diff --git a/public/modules/router.js b/public/modules/router.js new file mode 100644 index 0000000..5b36947 --- /dev/null +++ b/public/modules/router.js @@ -0,0 +1,10 @@ +export const navigate = (path) => { + /** + * Update the URL + */ + window.history.pushState({}, '', path); + /** + * Dispatch a popstate event + */ + window.dispatchEvent(new PopStateEvent('popstate')); +}; From 46095333046c940bce247ee707c89c0f82c369a9 Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Sun, 3 Nov 2024 12:09:43 +0300 Subject: [PATCH 12/35] Add button to header --- .../EventContentPage/EventContentPage.css | 34 +++++++++---------- .../EventContentPage/EventContentPage.js | 25 +++++++------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/public/components/EventContentPage/EventContentPage.css b/public/components/EventContentPage/EventContentPage.css index b1af074..61a81f2 100644 --- a/public/components/EventContentPage/EventContentPage.css +++ b/public/components/EventContentPage/EventContentPage.css @@ -40,28 +40,26 @@ } .image { - max-width: 100%; + max-width: 40%; height: auto; } .buttonDelete { - background-color: red; - color: white; - padding: 10px 20px; - border: none; - border-radius: 5px; - cursor: pointer; - font-size: 16px; - transition: background-color 0.3s; + background: white; + border: 2px solid red; + color:red; +} +.buttonDelete:active { + background: red; + color: white; } -.buttonDelete { - background-color: red; - color: white; - padding: 10px 20px; - border: none; - border-radius: 5px; - cursor: pointer; - font-size: 16px; - transition: background-color 0.3s; +.buttonEdit{ + background: white; + border: 2px solid rgb(255, 123, 0); + color:rgb(255, 123, 0); +} +.buttonEdit:active { + background: rgb(255, 123, 0); + color: white; } \ No newline at end of file diff --git a/public/components/EventContentPage/EventContentPage.js b/public/components/EventContentPage/EventContentPage.js index 9cb8c43..84fc373 100644 --- a/public/components/EventContentPage/EventContentPage.js +++ b/public/components/EventContentPage/EventContentPage.js @@ -1,4 +1,4 @@ -import { FrontendAPI } from "../../modules/FrontendAPI.js"; +import { api } from "../../modules/FrontendAPI.js"; export class EventContentPage { constructor(eventId) { @@ -47,7 +47,7 @@ export class EventContentPage { }; - renderEvent(event){ + _renderEvent(event){ console.log(event); console.log(event.title); @@ -66,7 +66,7 @@ export class EventContentPage { this.style.objectFit = 'fill'; }; - const descr = document.createElement('div'); + const descr = document.createElement('div'); /** render event */ //descr.className = 'title'; descr.textContent = event.description; @@ -82,8 +82,13 @@ export class EventContentPage { const btnDeleteEvent = document.createElement('button'); btnDeleteEvent.className = 'buttonDelete'; - btnDeleteEvent.textContent = 'Delete'; + btnDeleteEvent.textContent = 'Удалить мероприятие'; + const btnEditEvent = document.createElement('button'); + btnEditEvent.className = 'buttonEdit'; + btnEditEvent.textContent = 'Редактировать мероприятие'; + + actionsDiv.appendChild(btnEditEvent); actionsDiv.appendChild(btnDeleteEvent); const tagsDiv = document.createElement('div'); @@ -92,11 +97,11 @@ export class EventContentPage { const startDate = document.createElement('div'); startDate.className = 'event__date'; - startDate.textContent = 'Date start: ' + event.date_start; // Укажите вашу дату начала + startDate.textContent = 'Дата начала: ' + event.date_start; // Укажите вашу дату начала const endDate = document.createElement('div'); endDate.className = 'event__date'; - endDate.textContent = 'Date end: ' + event.date_end; // Укажите вашу дату окончания + endDate.textContent = 'Дата окончания: ' + event.date_end; // Укажите вашу дату окончания rightDiv.appendChild(actionsDiv); rightDiv.appendChild(tagsDiv); @@ -110,19 +115,13 @@ export class EventContentPage { } async renderTemplate(id) { console.log(id); - const api = new FrontendAPI(); const path = '/events/'+id.toString(); const request = {headers: {}}; try { const response = await api.get(path, request); const event = await response.json(); - console.log(event); - console.log("here"); - /** render event */ - this.renderEvent(event); - //this.leftPart = document.createElement('leftPart'); - //this.rightPart = document.createElement('rightPart'); + this._renderEvent(event); } catch (error) { console.log(error); From fc1c58871c9ac3809c75b16a5e97b3882e229d85 Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Sun, 3 Nov 2024 18:03:37 +0300 Subject: [PATCH 13/35] fixed frontend api --- public/components/Header/Header.js | 9 +++++++++ public/index.css | 2 ++ public/index.html | 1 + public/index.js | 28 +++++++++++++++++++++++++++- public/modules/FrontendAPI.js | 11 +++++++++-- 5 files changed, 48 insertions(+), 3 deletions(-) diff --git a/public/components/Header/Header.js b/public/components/Header/Header.js index f64481a..f21a872 100644 --- a/public/components/Header/Header.js +++ b/public/components/Header/Header.js @@ -116,6 +116,14 @@ export class Header { avatarImage.className = 'avatar'; profileLink.appendChild(avatarImage); buttons.appendChild(profileLink); + + const btnMyEvents = document.createElement('button'); + btnMyEvents.textContent = 'Мои мероприятия'; + btnMyEvents.addEventListener('click', (event) => { + event.preventDefault(); + const path = '/my_events'; + navigate(path); + }); /** * The logout button element. @@ -139,6 +147,7 @@ export class Header { console.error(error); } }; + buttons.appendChild(btnMyEvents); buttons.appendChild(logoutButton); } diff --git a/public/index.css b/public/index.css index e0a8b88..c39c002 100644 --- a/public/index.css +++ b/public/index.css @@ -3,6 +3,8 @@ @import "components/Nav/Nav.css"; @import "components/Footer/Footer.css"; @import "components/EventContentPage/EventContentPage.css"; +@import "components/UserEventsPage/UserEventsPage.css"; +UserEventsPage body { font-family: Arial, sans-serif; font-weight: bold; diff --git a/public/index.html b/public/index.html index 9d8a56e..71bdbec 100644 --- a/public/index.html +++ b/public/index.html @@ -15,6 +15,7 @@ + diff --git a/public/index.js b/public/index.js index b18583c..61e052c 100644 --- a/public/index.js +++ b/public/index.js @@ -11,10 +11,13 @@ import { Header } from "./components/Header/Header.js"; import { Nav } from "./components/Nav/Nav.js"; import { Feed } from "./components/Feed/Feed.js"; import { EventContentPage } from "./components/EventContentPage/EventContentPage.js"; +import { UserEventsPage } from "./components/UserEventsPage/UserEventsPage.js"; import { Footer } from "./components/Footer/Footer.js"; import { checkSession } from './modules/session.js'; import { handleRegisterSubmit, handleRegisterCheck } from './modules/registerForm.js'; import { handleLoginSubmit, handleLoginCheck } from './modules/loginForm.js'; +import { EventCreateForm } from "./components/EventCreateForm/EventCreateForm.js"; +import { handleCreateEventSubmit } from './modules/handleEventsActions.js'; /** * Get the root element @@ -167,6 +170,22 @@ const routes = { let eventPage = await new EventContentPage('event').renderTemplate(id); newsFeed.appendChild(eventPage); }, + '/my_events': async(id) => { + newsFeed.innerHTML = ''; // Clear the modal window content + let UserEventPage = await new UserEventsPage('userEvents').renderTemplate(id); + newsFeed.appendChild(UserEventPage); + }, + '/add_event': () => { + newsFeed.innerHTML = ''; // Clear the modal window content + const formCreate = new EventCreateForm().renderTemplate(); + newsFeed.appendChild(formCreate) + const createBtn = document.getElementById('eventSubmitBtn'); + + createBtn.addEventListener('click', (event) => handleCreateEventSubmit(event, '/my_events', navigate)); + //formCreate.addEventListener('submit', (event) => handleCreateEventSubmit(event, '/my_events', navigate)); + + //button to cancel creating + }, }; /** @@ -224,7 +243,14 @@ if (currentPath === '/login' || currentPath === '/signup') { */ const id = currentPath.split('/')[2]; routes['/events/:id'](id); // Вызываем обработчик с id - +} else if (currentPath === '/my_events') { + //(/\/events\d+/.test(currentPath)) + //let num = currentPath.match(/\d+/)[0]; + const num = 0; + /* somehow get current user id and check that user is logged in*/ + routes['/my_events'](num); +} else if (currentPath === '/add_event') { + routes['/add_event'](); } else { /** * Call the default route function diff --git a/public/modules/FrontendAPI.js b/public/modules/FrontendAPI.js index aa971cf..4beca4e 100644 --- a/public/modules/FrontendAPI.js +++ b/public/modules/FrontendAPI.js @@ -1,6 +1,6 @@ import { endpoint } from "../config.js"; -export class FrontendAPI { +class FrontendAPI { /* * {path, headers = {}, needCredentials = false, id = null} object like request @@ -18,6 +18,11 @@ export class FrontendAPI { });*/ } + post(path, request) { + request['method'] = 'POST'; + return this._commonFetchRequest(path, request); + } + _removeNullUndefined(obj) { for (const key in obj) { if (obj[key] === null || obj[key] === undefined) { @@ -70,4 +75,6 @@ export class FrontendAPI { //const resp = await response.json(); return response; } -} \ No newline at end of file +} + +export const api = new FrontendAPI(); \ No newline at end of file From e7fbda3590bcee358b7d781c3789b582cc3950a2 Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Sun, 3 Nov 2024 18:05:07 +0300 Subject: [PATCH 14/35] edit login --- public/components/Login/Login.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/components/Login/Login.js b/public/components/Login/Login.js index 4a74b9f..703554b 100644 --- a/public/components/Login/Login.js +++ b/public/components/Login/Login.js @@ -211,8 +211,8 @@ export class LoginForm { renderTemplate() { const template = Handlebars.templates['Login.hbs']; const config = this.config; - let itemss = Object.entries(config); - let items = itemss.map(([key, {tag, text, className, type}], index) => { + let itemsArray = Object.entries(config); + let items = itemsArray.map(([key, {tag, text, className, type}], index) => { let needPlaceholder = (tag === 'input'); return {key, tag, text, className, type, needPlaceholder}; }); From dba7a6fe37b39e14b9a1c9ce881506f82712cc0a Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Thu, 7 Nov 2024 20:39:39 +0300 Subject: [PATCH 15/35] changed registration, ADD: create form event --- .../EventCreateForm/EventCreateForm.css | 4 + .../EventCreateForm/EventCreateForm.hbs | 16 ++-- .../EventCreateForm/EventCreateForm.js | 80 +++++++++++++++---- public/components/Register/Register.js | 23 ++++++ public/index.js | 10 ++- public/modules/handleEventsActions.js | 61 +++++++++++--- public/modules/registerForm.js | 17 +++- 7 files changed, 177 insertions(+), 34 deletions(-) diff --git a/public/components/EventCreateForm/EventCreateForm.css b/public/components/EventCreateForm/EventCreateForm.css index e69de29..dbd336d 100644 --- a/public/components/EventCreateForm/EventCreateForm.css +++ b/public/components/EventCreateForm/EventCreateForm.css @@ -0,0 +1,4 @@ +.edit_container { + display: flex; + align-items: center; +} \ No newline at end of file diff --git a/public/components/EventCreateForm/EventCreateForm.hbs b/public/components/EventCreateForm/EventCreateForm.hbs index 640af3c..7827ed5 100644 --- a/public/components/EventCreateForm/EventCreateForm.hbs +++ b/public/components/EventCreateForm/EventCreateForm.hbs @@ -1,7 +1,13 @@ {{#each items}} - {{#if this.needPlaceholder}} - <{{this.tag}} class="{{this.className}}" id = "{{this.key}}" placeholder = "{{this.text}}" type="{{this.type}}" data-section="{{this.key}}"> - {{else}} - <{{this.tag}} class="{{this.className}}" id = "{{this.key}}" data-section="{{this.key}}">{{this.text}} - {{/if}} + + {{#if this.needPlaceholder}} +
+ + <{{this.tag}} class="{{this.className}}" id = "{{this.key}}" placeholder = "{{this.text}}" type="{{this.type}}" data-section="{{this.key}}"> +
+ + {{else}} + <{{this.tag}} class="{{this.className}}" id = "{{this.key}}" data-section="{{this.key}}">{{this.text}} + {{/if}} + {{/each}} \ No newline at end of file diff --git a/public/components/EventCreateForm/EventCreateForm.js b/public/components/EventCreateForm/EventCreateForm.js index 96e44bd..943de6c 100644 --- a/public/components/EventCreateForm/EventCreateForm.js +++ b/public/components/EventCreateForm/EventCreateForm.js @@ -1,6 +1,7 @@ /** * LoginForm class */ +import { api } from '../../modules/FrontendAPI.js'; export class EventCreateForm { constructor(formId) { @@ -10,7 +11,7 @@ export class EventCreateForm { config = { eventServerError: { - text: 'очень много текста на неизвестном древнем языке', + text: '', tag: 'label', className: 'error_text', type: '', @@ -48,6 +49,9 @@ export class EventCreateForm { * @type {string} */ className: '', + + + }, eventNameError: { @@ -59,7 +63,7 @@ export class EventCreateForm { eventDescriptionEntry: { text: 'Описание мероприятия', - tag: 'input', + tag: 'textarea', type: '', @@ -110,18 +114,18 @@ export class EventCreateForm { type: '', }, - eventBeginEntry: { + eventBeginEntry: { text: 'Время начала мероприятия', tag: 'input', - type: '', + type: 'datetime-local', className: '', - }, - - eventBeginError: { + }, + + eventBeginError: { text: '', @@ -130,7 +134,7 @@ export class EventCreateForm { className: 'error_text', type: '', - }, + }, eventEndEntry: { /** @@ -147,14 +151,14 @@ export class EventCreateForm { * Type * @type {string} */ - type: '', + type: 'datetime-local', /** * Class name * @type {string} */ className: '', }, - + eventEndError: { /** * Error text @@ -177,6 +181,48 @@ export class EventCreateForm { */ type: '', }, + imageInput: { + /** + * Error text + * @type {string} + */ + text: '', + /** + * Tag type + * @type {string} + */ + tag: 'input', + /** + * Class name + * @type {string} + */ + className: '', + /** + * Type + * @type {string} + */ + type: 'file', + accept: "image/png, image/jpeg" + }, + categories: { + + text: '', + /** + * Tag type + * @type {string} + */ + tag: 'div', + /** + * Class name + * @type {string} + */ + className: '', + /** + * Type + * @type {string} + */ + type: '', + }, eventSubmitBtn: { @@ -194,19 +240,24 @@ export class EventCreateForm { * Renders the form template * @returns {HTMLFormElement} The rendered form */ - renderTemplate() { + renderTemplate(selectElement) { const template = Handlebars.templates['EventCreateForm.hbs']; - console.log( template ); + const config = this.config; let itemsArray = Object.entries(config); let items = itemsArray.map(([key, {tag, text, className, type}], index) => { let needPlaceholder = (tag === 'input'); - return {key, tag, text, className, type, needPlaceholder}; + let needMaxMinTime = (type === 'time'); + return {key, tag, text, className, type, needPlaceholder, needMaxMinTime}; }); console.log(items); this.form.innerHTML += template({items}); + + const categoriesSelect = selectElement; + selectElement.id = 'categoriesInput'; + this.form.appendChild(categoriesSelect); //const createBtn = document.getElementById('createBtn'); //createBtn.addEventListener(); @@ -214,4 +265,5 @@ export class EventCreateForm { return this.form; } } - \ No newline at end of file + + \ No newline at end of file diff --git a/public/components/Register/Register.js b/public/components/Register/Register.js index 0db280f..95afff5 100644 --- a/public/components/Register/Register.js +++ b/public/components/Register/Register.js @@ -194,6 +194,29 @@ export class RegisterForm { */ type: '', }, + imageInput: { + /** + * Error text + * @type {string} + */ + text: '', + /** + * Tag type + * @type {string} + */ + tag: 'input', + /** + * Class name + * @type {string} + */ + className: '', + /** + * Type + * @type {string} + */ + type: 'file', + accept: "image/png, image/jpeg" + }, /** * Submit button configuration diff --git a/public/index.js b/public/index.js index 61e052c..1e8eed7 100644 --- a/public/index.js +++ b/public/index.js @@ -17,7 +17,7 @@ import { checkSession } from './modules/session.js'; import { handleRegisterSubmit, handleRegisterCheck } from './modules/registerForm.js'; import { handleLoginSubmit, handleLoginCheck } from './modules/loginForm.js'; import { EventCreateForm } from "./components/EventCreateForm/EventCreateForm.js"; -import { handleCreateEventSubmit } from './modules/handleEventsActions.js'; +import { handleCreateEventSubmit, loadCategories } from './modules/handleEventsActions.js'; /** * Get the root element @@ -175,10 +175,12 @@ const routes = { let UserEventPage = await new UserEventsPage('userEvents').renderTemplate(id); newsFeed.appendChild(UserEventPage); }, - '/add_event': () => { + '/add_event': async() => { newsFeed.innerHTML = ''; // Clear the modal window content - const formCreate = new EventCreateForm().renderTemplate(); - newsFeed.appendChild(formCreate) + const categSelect = await loadCategories(); + const formCreate = new EventCreateForm().renderTemplate(categSelect); + console.log(formCreate); + newsFeed.appendChild(formCreate); const createBtn = document.getElementById('eventSubmitBtn'); createBtn.addEventListener('click', (event) => handleCreateEventSubmit(event, '/my_events', navigate)); diff --git a/public/modules/handleEventsActions.js b/public/modules/handleEventsActions.js index b0ab553..043be4b 100644 --- a/public/modules/handleEventsActions.js +++ b/public/modules/handleEventsActions.js @@ -20,6 +20,7 @@ import { isValidUsername, isValidPassword, isValidEmail, removeDangerous } from import { endpoint } from "../../config.js" import { api } from './FrontendAPI.js'; +console.log(api); /** * Error message for empty fields. * @constant {string} @@ -55,18 +56,47 @@ const INCORRECT_EMAIL = 'Адрес email должен содержать нес * @param {function} setUserLoggedIn - A function to set the user's logged-in state. * @param {function} navigate - A function to navigate to a different page. */ + +export async function loadCategories() { + const selectElement = document.createElement('select'); + console.log(selectElement); + try { + const request = { headers: {} }; + console.log(api); + const response = await api.get('/categories', request); + const categories = await response.json(); + + console.log(categories); + + // Заполнение выпадающего списка + categories.forEach(category => { + const option = document.createElement('option'); + option.value = category.id; // id категории + option.textContent = category.name; // название категории + selectElement.appendChild(option); + }); + return selectElement; + } catch (error) { + console.error('Ошибка при загрузке категорий:', error); + } + return selectElement; +} + export async function handleCreateEventSubmit(event, pageToCome, navigate) { event.preventDefault(); - console.log('click'); + loadCategories(); // Get form data const title = removeDangerous(document.getElementById('eventNameEntry').value); const description = removeDangerous(document.getElementById('eventDescriptionEntry').value); - const tags = removeDangerous(document.getElementById('eventTagsEntry').value); - const dateStart = removeDangerous(document.getElementById('eventBeginEntry').value); - const dateEnd = removeDangerous(document.getElementById('eventEndEntry').value); - console.log(title, description, tags, dateStart, dateEnd); + const tags = removeDangerous(document.getElementById('eventTagsEntry').value).split(); + const dateStart = removeDangerous(document.getElementById('eventBeginEntry').value) + ':00Z'; + const dateEnd = removeDangerous(document.getElementById('eventEndEntry').value) + ':00Z'; + + const categoryId = Number(removeDangerous(document.getElementById('categoriesInput').value)); + + const image = document.getElementById('imageInput').files[0]; + console.log(title, description, tags, dateStart, dateEnd, image, categoryId); /* - // Clear error messages document.getElementById('registerUsernameError').innerText = ''; document.getElementById('registerPasswordError').innerText = ''; @@ -109,13 +139,26 @@ export async function handleCreateEventSubmit(event, pageToCome, navigate) { */ try { // Send request to backend - const body = { title, description, tags, dateStart, dateEnd }; + const userData = { + title: title, + description: description, + tags: tags, + event_start: dateStart, + event_end: dateEnd, + category_id: categoryId, + }; + + const json = JSON.stringify(userData); + const formData = new FormData(); + formData.append('json', json); + formData.append('image', image); + const body = formData; const request = { headers: { - 'Content-Type': 'application/json', + }, credentials: 'include', - body: JSON.stringify(body), + body: body, }; const path = '/events'; const response = await api.post(path, request); diff --git a/public/modules/registerForm.js b/public/modules/registerForm.js index 9a68d8e..2f79a52 100644 --- a/public/modules/registerForm.js +++ b/public/modules/registerForm.js @@ -66,6 +66,8 @@ export async function handleRegisterSubmit(event, setUserLoggedIn, navigate) { const username = removeDangerous(document.getElementById('registerUsernameEntry').value); const email = removeDangerous(document.getElementById('registerEmailEntry').value); const password = removeDangerous(document.getElementById('registerPasswordEntry').value); + const image = document.getElementById('imageInput').files[0] + console.log(image); // Initialize validation flag let isValid = true; @@ -97,14 +99,25 @@ export async function handleRegisterSubmit(event, setUserLoggedIn, navigate) { } try { + const userData = { + username: username, + email: email, + password: password, + }; + + const json = JSON.stringify(userData); + const formData = new FormData(); + formData.append('json', json); + formData.append('image', image); + console.log(formData); // Send request to backend const response = await fetch(`${endpoint}/register`, { method: 'POST', headers: { - 'Content-Type': 'application/json', + }, credentials: 'include', - body: JSON.stringify({ username, email, password }), + body: formData, }); // If response is not OK, throw error if (!response.ok) { From 94b6c84400213b656094203f88b665f485e0e30c Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Thu, 7 Nov 2024 20:41:48 +0300 Subject: [PATCH 16/35] fixed event form lint warnings --- public/components/EventCreateForm/EventCreateForm.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/public/components/EventCreateForm/EventCreateForm.js b/public/components/EventCreateForm/EventCreateForm.js index 943de6c..310f23e 100644 --- a/public/components/EventCreateForm/EventCreateForm.js +++ b/public/components/EventCreateForm/EventCreateForm.js @@ -50,8 +50,6 @@ export class EventCreateForm { */ className: '', - - }, eventNameError: { @@ -265,5 +263,4 @@ export class EventCreateForm { return this.form; } } - - \ No newline at end of file + \ No newline at end of file From ab06827b331d1c6c55f8f849ee351bb3c52d3805 Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Thu, 7 Nov 2024 20:42:56 +0300 Subject: [PATCH 17/35] categore select handler --- .../CategorySelect/CategorySelect.hbs | 0 .../CategorySelect/CategorySelect.js | 22 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 public/components/CategorySelect/CategorySelect.hbs create mode 100644 public/components/CategorySelect/CategorySelect.js diff --git a/public/components/CategorySelect/CategorySelect.hbs b/public/components/CategorySelect/CategorySelect.hbs new file mode 100644 index 0000000..e69de29 diff --git a/public/components/CategorySelect/CategorySelect.js b/public/components/CategorySelect/CategorySelect.js new file mode 100644 index 0000000..48dc910 --- /dev/null +++ b/public/components/CategorySelect/CategorySelect.js @@ -0,0 +1,22 @@ +async function loadCategories() { + const selectElement = document.createElement('select'); + try { + const request = { headers: {} }; + const response = await api.get('/categories', request); + const categories = await response.json(); + + console.log(categories); + + // Заполнение выпадающего списка + categories.forEach(category => { + const option = document.createElement('option'); + option.value = category.id; // id категории + option.textContent = category.name; // название категории + selectElement.appendChild(option); + }); + + } catch (error) { + console.error('Ошибка при загрузке категорий:', error); + } + return categorySelect; +} \ No newline at end of file From cfdc55b2eb95c5aee89935bfeec1647a5090082b Mon Sep 17 00:00:00 2001 From: Rassmagin Alexander Date: Thu, 7 Nov 2024 23:11:48 +0300 Subject: [PATCH 18/35] Profile --- public/components/Header/Header.js | 1 + public/components/Profile/Profile.css | 85 ++++++++++++++ public/components/Profile/Profile.js | 163 ++++++++++++++++++++++++++ public/index.css | 1 + public/index.js | 9 +- 5 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 public/components/Profile/Profile.css create mode 100644 public/components/Profile/Profile.js diff --git a/public/components/Header/Header.js b/public/components/Header/Header.js index f21a872..a0db045 100644 --- a/public/components/Header/Header.js +++ b/public/components/Header/Header.js @@ -106,6 +106,7 @@ export class Header { * @type {HTMLElement} */ const profileLink = document.createElement('a'); + profileLink.href = '/profile'; const avatarImage = document.createElement('img'); avatarImage.src = '/static/images/myavatar.png'; avatarImage.onerror = function() { diff --git a/public/components/Profile/Profile.css b/public/components/Profile/Profile.css new file mode 100644 index 0000000..3d7a8e3 --- /dev/null +++ b/public/components/Profile/Profile.css @@ -0,0 +1,85 @@ +/* Общие стили для контейнера профиля */ +#profileContent { + display: flex; + flex-direction: column; + align-items: center; + max-width: 800px; + min-height: 300px; + margin: 50px auto; + padding: 20px; + background-color: lightgray; + border-radius: 10px; + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); +} + +/* Контейнер для фото и формы */ +.profile-container { + display: flex; + align-items: flex-start; +} + +/* Стили для контейнера фото профиля */ +.profile-picture-container { + margin-right: 20px; +} + +/* Стили для фото профиля */ +#profileImage { + width: 150px; /* Ширина фото */ + height: 150px; /* Высота фото */ + border-radius: 50%; /* Округление */ + object-fit: cover; /* Обрезка изображения */ +} + +/* Стили для формы */ +.form-container { + display: flex; + flex-direction: column; + width: 100%; /* Занимает оставшееся пространство */ +} + +/* Стили для меток */ +#profileContent label { + font-size: 18px; + font-weight: bold; + margin-bottom: 10px; + color: black; + display: block; + text-align: left; + width: 100%; +} + +/* Стили для полей ввода */ +#profileContent input { + width: 100%; + padding: 10px; + margin-bottom: 20px; + font-size: 16px; + border: 1px solid #ccc; + border-radius: 5px; + background-color: #fff; + box-sizing: border-box; +} + +/* Стили для кнопки сохранения */ +#profileContent button { + background-color: black; + color: white; + padding: 10px 15px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 16px; +} + +/* Эффект при наведении на кнопку */ +#profileContent button:hover { + background-color: #444; +} + +/* Стили для текста ошибок */ +.error_text { + color: #ff4d4d; + font-size: 14px; + margin-top: 10px; +} diff --git a/public/components/Profile/Profile.js b/public/components/Profile/Profile.js new file mode 100644 index 0000000..fd0ce77 --- /dev/null +++ b/public/components/Profile/Profile.js @@ -0,0 +1,163 @@ +import { endpoint } from "../../config.js"; + +export class Profile { + renderProfile() { + const profilePage = document.createElement('div'); + profilePage.id = 'profilePage'; + const profileContent = document.createElement('div'); + profileContent.id = 'profileContent'; + + const profileContainer = document.createElement('div'); + profileContainer.classList.add('profile-container'); + + const profilePictureContainer = document.createElement('div'); + profilePictureContainer.classList.add('profile-picture-container'); + + const profilePicture = document.createElement('img'); + profilePicture.id = 'profileImage'; + profilePicture.onerror = function() { + this.src = "/static/images/default_avatar.png"; + this.style.objectFit = 'fill'; + }; + profilePicture.alt = 'Profile Picture'; + + // Button to upload new image + const uploadButton = document.createElement('input'); + uploadButton.type = 'file'; + uploadButton.accept = 'image/*'; + uploadButton.addEventListener('change', this.uploadProfilePicture.bind(this)); + + profilePictureContainer.appendChild(profilePicture); + profilePictureContainer.appendChild(uploadButton); + + const formContainer = document.createElement('div'); + formContainer.classList.add('form-container'); + + // Unchangeable fields + const unchangeableFields = [ + { label: 'Username', id: 'username' }, + { label: 'Email', id: 'email' } + ]; + + unchangeableFields.forEach(field => { + const fieldContainer = document.createElement('div'); + const label = document.createElement('label'); + label.textContent = field.label; + label.setAttribute('for', field.id); + + const input = document.createElement('input'); + input.type = 'text'; + input.id = field.id; + input.readOnly = true; // Make the field read-only + + fieldContainer.appendChild(label); + fieldContainer.appendChild(input); + formContainer.appendChild(fieldContainer); + }); + + // Changeable fields + const changeableFields = [ + { label: 'Name', id: 'name', type: 'text' }, + { label: 'Surname', id: 'surname', type: 'text' }, + // { label: 'Password', id: 'password', type: 'password' }, + ]; + + changeableFields.forEach(field => { + const fieldContainer = document.createElement('div'); + const label = document.createElement('label'); + label.textContent = field.label; + label.setAttribute('for', field.id); + + const input = document.createElement('input'); + input.type = field.type; + input.id = field.id; + + fieldContainer.appendChild(label); + fieldContainer.appendChild(input); + formContainer.appendChild(fieldContainer); + }); + + const saveButton = document.createElement('button'); + saveButton.textContent = 'Save Changes'; + saveButton.addEventListener('click', this.saveChanges.bind(this)); + formContainer.appendChild(saveButton); + + profileContainer.appendChild(profilePictureContainer); + profileContainer.appendChild(formContainer); + profileContent.appendChild(profileContainer); + + this.fetchProfileData().then(profileData => { + if (profileData) { + document.getElementById('username').value = profileData.username; + document.getElementById('email').value = profileData.email; + document.getElementById('name').value = profileData.name || ''; + document.getElementById('surname').value = profileData.surname || ''; + profilePicture.src = profileData.profilePictureUrl || '/static/images/default_avatar.png'; + } + }).catch(error => { + console.error('Error fetching profile data:', error); + }); + + profilePage.append(profileContent); + return profilePage; + } + + async uploadProfilePicture(event) { + const file = event.target.files[0]; + if (file) { + const formData = new FormData(); + formData.append('profilePicture', file); + } + } + + async fetchProfileData() { + try { + const response = await fetch(`${endpoint}/profile`, { + method: 'GET', + credentials: 'include', + }); + + if (!response.ok) { + throw new Error('Failed to fetch profile data'); + } + + return await response.json(); + } catch (error) { + console.error('Error fetching profile data:', error); + } + } + + async saveChanges() { + const username = document.getElementById('username').value; + const password = document.getElementById('password').value; + const email = document.getElementById('email').value; + const name = document.getElementById('name').value; + const surname = document.getElementById('surname').value; + const dob = document.getElementById('dob').value; + + const formData = new FormData(); + formData.append('username', username); + formData.append('password', password); + formData.append('email', email); + formData.append('name', name); + formData.append('surname', surname); + formData.append('dob', dob); + + try { + const response = await fetch(`${endpoint}/profile`, { + method: 'PUT', + json: {email: email, username: username}, + }); + + if (response.ok) { + alert('Profile updated successfully!'); + } else { + const errorText = await response.json(); + alert(`Error updating profile: ${errorText.message}`); + } + } catch (error) { + console.error('Error saving profile data:', error); + } + } +} + diff --git a/public/index.css b/public/index.css index c39c002..ea08da0 100644 --- a/public/index.css +++ b/public/index.css @@ -2,6 +2,7 @@ @import "components/Header/Header.css"; @import "components/Nav/Nav.css"; @import "components/Footer/Footer.css"; +@import "components/Profile/Profile.css"; @import "components/EventContentPage/EventContentPage.css"; @import "components/UserEventsPage/UserEventsPage.css"; UserEventsPage diff --git a/public/index.js b/public/index.js index 1e8eed7..0f1f782 100644 --- a/public/index.js +++ b/public/index.js @@ -10,6 +10,7 @@ import { RegisterForm } from "./components/Register/Register.js"; import { Header } from "./components/Header/Header.js"; import { Nav } from "./components/Nav/Nav.js"; import { Feed } from "./components/Feed/Feed.js"; +import { Profile } from "./components/Profile/Profile.js"; import { EventContentPage } from "./components/EventContentPage/EventContentPage.js"; import { UserEventsPage } from "./components/UserEventsPage/UserEventsPage.js"; import { Footer } from "./components/Footer/Footer.js"; @@ -165,6 +166,12 @@ const routes = { let feed = await new Feed().renderFeed(); newsFeed.appendChild(feed); }, + '/profile': () => { + newsFeed.innerHTML = ''; // Clear the modal window content + const profile = new Profile(); + const profileElement = profile.renderProfile(); + newsFeed.appendChild(profileElement); + }, '/events/:id': async(id) => { newsFeed.innerHTML = ''; // Clear the modal window content let eventPage = await new EventContentPage('event').renderTemplate(id); @@ -223,7 +230,7 @@ const currentPath = window.location.pathname; /** * Check if the current path is the login or signup page */ -if (currentPath === '/login' || currentPath === '/signup') { +if (currentPath === '/login' || currentPath === '/signup' || currentPath == '/profile' || currentPath == '/search') { /** * Get the route for the current path */ From e70c5e198534bce3fbed27abb0f906b8810ea2bd Mon Sep 17 00:00:00 2001 From: Rassmagin Alexander Date: Thu, 7 Nov 2024 23:30:28 +0300 Subject: [PATCH 19/35] Search --- public/components/Header/Header.js | 12 ++- public/components/Search/Search.css | 66 ++++++++++++++ public/components/Search/Search.js | 134 ++++++++++++++++++++++++++++ public/index.css | 1 + public/index.js | 8 +- 5 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 public/components/Search/Search.css create mode 100644 public/components/Search/Search.js diff --git a/public/components/Header/Header.js b/public/components/Header/Header.js index a0db045..07a55f0 100644 --- a/public/components/Header/Header.js +++ b/public/components/Header/Header.js @@ -59,7 +59,14 @@ export class Header { searchbar.type = 'search'; searchbar.className = 'searchbar'; searchbar.placeholder = 'Найти событие'; - searchbar.setAttribute('disabled', ""); + // Add event listener to detect Enter key press + searchbar.addEventListener('keydown', (event) => { + if (event.key === 'Enter') { // Check if the pressed key is Enter + event.preventDefault(); + const searchQuery = searchbar.value; + navigate(`/search?q=${encodeURIComponent(searchQuery)}`); + } + }); headerElement.appendChild(searchbar); headerElement.appendChild(searchbar); @@ -98,7 +105,8 @@ export class Header { btnRegister.className = "btnRegister"; btnRegister.textContent = "Зарегистрироваться"; buttons.appendChild(btnRegister); - } else { + } + if (userIsLoggedIn) { //User is logged in /** * The profile link element. diff --git a/public/components/Search/Search.css b/public/components/Search/Search.css new file mode 100644 index 0000000..8fa1da1 --- /dev/null +++ b/public/components/Search/Search.css @@ -0,0 +1,66 @@ +/* Search.css */ + +/* Style for the main search page container */ +#searchPage { + padding: 20px; + background-color: #f9f9f9; /* Light background color */ +} + +/* Style for the search parameters container */ +.search-parameters { + display: flex; + background-color: lightgray; + flex-direction: column; /* Stack elements vertically */ + width: 100%; /* Full width */ + margin: 20px 0; /* Space above and below the search parameters */ + padding: 20px; /* Add padding inside the container */ + border-radius: 10px; /* Curvy corners */ + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); /* Subtle shadow for depth */ +} + +/* Style for input labels */ +.input-label { + font-weight: bold; /* Make labels bold */ + margin-bottom: 5px; /* Space between label and input */ +} + +/* Style for tags input */ +.tags-input, .search-input { + padding: 10px; /* Padding inside the input */ + border: 1px solid #ccc; /* Border style */ + border-radius: 5px; /* Rounded corners */ + margin-bottom: 15px; /* Space between inputs */ + width: 90%; /* Set width to 90% of the parent container */ + box-sizing: border-box; /* Include padding and border in width */ +} + +/* Style for the feed content area */ +#feedContent { + margin-top: 20px; /* Space above the feed content */ +} + +/* Optional: Style for individual feed elements */ +.feed-element { + padding: 15px; /* Padding for each feed element */ + border: 1px solid #e0e0e0; /* Light border */ + border-radius: 4px; /* Rounded corners */ + margin-bottom: 10px; /* Space between feed elements */ + background-color: #fff; /* White background */ +} + +/* Optional: Hover effect for feed elements */ +.feed-element:hover { + background-color: #f1f1f1; /* Light grey on hover */ +} + +/* Additional styling for responsiveness */ +@media (max-width: 600px) { + .search-parameters { + flex-direction: column; /* Stack inputs vertically on small screens */ + } + + .tags-input, + .search-input { + margin-bottom: 10px; /* Space between inputs */ + } +} diff --git a/public/components/Search/Search.js b/public/components/Search/Search.js new file mode 100644 index 0000000..3178dbc --- /dev/null +++ b/public/components/Search/Search.js @@ -0,0 +1,134 @@ +/** + * Import the endpoint configuration from the config.js file + * @import {string} endpoint - The API endpoint URL + */ +import { endpoint } from "../../config.js"; +/** + * Import the FeedElement component from the FeedElement.js file + * @import FeedElement - A component representing a feed element + */ +import { FeedElement } from "../FeedElement/FeedElement.js"; +/** + * Search module. + * + * This module provides a class to render a search feed of events. + * + * @module search + */ + +/** + * Search class. + * + * This class is responsible for rendering a search feed of events. + * + * @class Search + */ +export class Search { + /** + * Renders the search feed of events. + * + * This method fetches the events from the server, creates a FeedElement for each event, and appends them to the feed content. + * + * @async + * @method renderSearch + * @returns {HTMLElement} The search feed content element. + */ + async renderSearch(navigate, searchQuery) { + // Create the main container for the search page + const searchPage = document.createElement('div'); + searchPage.id = 'searchPage'; + + // Create the search parameters container + const searchParameters = document.createElement('div'); + searchParameters.id = 'searchParameters'; + searchParameters.className = 'search-parameters'; // Add a class for styling + + // Create the Tags title and input field + const tagsLabel = document.createElement('label'); + tagsLabel.textContent = 'Tags'; + tagsLabel.className = 'input-label'; // Add a class for styling + const tagsInput = document.createElement('input'); + tagsInput.type = 'text'; + tagsInput.placeholder = 'Enter tags...'; // Placeholder text for Tags input + tagsInput.className = 'tags-input'; // Add a class for styling + // Add event listener to detect Enter key press + tagsInput.addEventListener('keydown', (event) => { + if (event.key === 'Enter') { // Check if the pressed key is Enter + event.preventDefault(); + const newTags = tagsInput.value; + const newSearchTerm = searchInput.value; + navigate(`/search?tags=${encodeURIComponent(newTags)}&q=${encodeURIComponent(newSearchTerm)}`); + } + }); + // Create the Search title and input field + const searchLabel = document.createElement('label'); + searchLabel.textContent = 'Search'; + searchLabel.className = 'input-label'; // Add a class for styling + const searchInput = document.createElement('input'); + searchInput.type = 'text'; + searchInput.placeholder = 'Enter search term...'; // Placeholder text for Search input + searchInput.className = 'search-input'; // Add a class for styling + searchInput.addEventListener('keydown', (event) => { + if (event.key === 'Enter') { // Check if the pressed key is Enter + event.preventDefault(); + const newSearchTerm = searchInput.value; + const newTags = tagsInput.value; + navigate(`/search?tags=${encodeURIComponent(newTags)}&q=${encodeURIComponent(newSearchTerm)}`); + } + }); + // Parse the searchQuery to extract tags and search term + const params = new URLSearchParams(searchQuery); + + const tags = params.get('tags') ? params.get('tags').split(' ') : []; // Split tags by space + const searchTerm = params.get('q') || ''; // Get the search term + console.log("Tags: ", tags); + console.log("searchTerms: ", searchTerm); + // Set the value of the input fields + tagsInput.value = tags.join(' '); // Join tags with space for display + searchInput.value = searchTerm; // Set search term + + // Append the titles and input fields to the searchParameters container + searchParameters.appendChild(tagsLabel); + searchParameters.appendChild(tagsInput); + searchParameters.appendChild(searchLabel); + searchParameters.appendChild(searchInput); + + // Append the searchParameters to the searchPage + searchPage.appendChild(searchParameters); + + // Create the feed content element + const feedContent = document.createElement('div'); + feedContent.id = 'feedContent'; + + // Fetch the feed from the server + const fetchFeed = async (tags, searchTerm) => { + const response = await fetch(`${endpoint}/events`, { + method: "GET", + headers: { + + }, + SearchRequest: { + query: searchTerm, + tags: tags, + } + }); + + if (response.ok) { + const feed = await response.json(); + Object.entries(feed.events).forEach(([key, { description, id }]) => { + const feedElement = new FeedElement(key, description, id, navigate).renderTemplate(); + feedContent.appendChild(feedElement); + }); + } else { + const errorText = await response.json(); + console.error('Error fetching feed:', errorText); + } + }; + + await fetchFeed(tags, searchTerm); // Calls the fetchFeed function + + // Append the feed content to the main search page + searchPage.appendChild(feedContent); + return searchPage; // Returns the search page element + } +} diff --git a/public/index.css b/public/index.css index ea08da0..34a3b0f 100644 --- a/public/index.css +++ b/public/index.css @@ -5,6 +5,7 @@ @import "components/Profile/Profile.css"; @import "components/EventContentPage/EventContentPage.css"; @import "components/UserEventsPage/UserEventsPage.css"; +@import "components/Search/Search.css"; UserEventsPage body { font-family: Arial, sans-serif; diff --git a/public/index.js b/public/index.js index 0f1f782..a82560b 100644 --- a/public/index.js +++ b/public/index.js @@ -11,6 +11,7 @@ import { Header } from "./components/Header/Header.js"; import { Nav } from "./components/Nav/Nav.js"; import { Feed } from "./components/Feed/Feed.js"; import { Profile } from "./components/Profile/Profile.js"; +import { Search } from "./components/Search/Search.js"; import { EventContentPage } from "./components/EventContentPage/EventContentPage.js"; import { UserEventsPage } from "./components/UserEventsPage/UserEventsPage.js"; import { Footer } from "./components/Footer/Footer.js"; @@ -182,6 +183,11 @@ const routes = { let UserEventPage = await new UserEventsPage('userEvents').renderTemplate(id); newsFeed.appendChild(UserEventPage); }, + '/search': async() => { + newsFeed.innerHTML = ''; // Clear the modal window content + let feed = await new Search().renderSearch(navigate, window.location.search.substring(1)); + newsFeed.appendChild(feed); + }, '/add_event': async() => { newsFeed.innerHTML = ''; // Clear the modal window content const categSelect = await loadCategories(); @@ -226,7 +232,7 @@ window.addEventListener('popstate', () => { * Check the current path when the page is loaded */ const currentPath = window.location.pathname; - +console.log(currentPath); /** * Check if the current path is the login or signup page */ From 44e01bb855f108a177c824a86eb200c1ddc60c80 Mon Sep 17 00:00:00 2001 From: Rassmagin Alexander Date: Fri, 8 Nov 2024 01:44:10 +0300 Subject: [PATCH 20/35] PUT for Profile email&username --- public/components/Profile/Profile.js | 78 ++++++++++++---------------- public/modules/FrontendAPI.js | 20 +++---- 2 files changed, 42 insertions(+), 56 deletions(-) diff --git a/public/components/Profile/Profile.js b/public/components/Profile/Profile.js index fd0ce77..0260c8a 100644 --- a/public/components/Profile/Profile.js +++ b/public/components/Profile/Profile.js @@ -1,4 +1,5 @@ import { endpoint } from "../../config.js"; +import { api } from '../../modules/FrontendAPI.js'; export class Profile { renderProfile() { @@ -34,34 +35,11 @@ export class Profile { formContainer.classList.add('form-container'); // Unchangeable fields - const unchangeableFields = [ + const changeableFields = [ { label: 'Username', id: 'username' }, { label: 'Email', id: 'email' } ]; - unchangeableFields.forEach(field => { - const fieldContainer = document.createElement('div'); - const label = document.createElement('label'); - label.textContent = field.label; - label.setAttribute('for', field.id); - - const input = document.createElement('input'); - input.type = 'text'; - input.id = field.id; - input.readOnly = true; // Make the field read-only - - fieldContainer.appendChild(label); - fieldContainer.appendChild(input); - formContainer.appendChild(fieldContainer); - }); - - // Changeable fields - const changeableFields = [ - { label: 'Name', id: 'name', type: 'text' }, - { label: 'Surname', id: 'surname', type: 'text' }, - // { label: 'Password', id: 'password', type: 'password' }, - ]; - changeableFields.forEach(field => { const fieldContainer = document.createElement('div'); const label = document.createElement('label'); @@ -88,10 +66,9 @@ export class Profile { this.fetchProfileData().then(profileData => { if (profileData) { + console.log(profileData); document.getElementById('username').value = profileData.username; document.getElementById('email').value = profileData.email; - document.getElementById('name').value = profileData.name || ''; - document.getElementById('surname').value = profileData.surname || ''; profilePicture.src = profileData.profilePictureUrl || '/static/images/default_avatar.png'; } }).catch(error => { @@ -106,7 +83,9 @@ export class Profile { const file = event.target.files[0]; if (file) { const formData = new FormData(); - formData.append('profilePicture', file); + formData.append('image', file); + const body = formData; + document.getElementById('profileImage').value = file; } } @@ -129,25 +108,36 @@ export class Profile { async saveChanges() { const username = document.getElementById('username').value; - const password = document.getElementById('password').value; const email = document.getElementById('email').value; - const name = document.getElementById('name').value; - const surname = document.getElementById('surname').value; - const dob = document.getElementById('dob').value; - - const formData = new FormData(); - formData.append('username', username); - formData.append('password', password); - formData.append('email', email); - formData.append('name', name); - formData.append('surname', surname); - formData.append('dob', dob); - + const image = document.getElementById('profileImage').value; try { - const response = await fetch(`${endpoint}/profile`, { - method: 'PUT', - json: {email: email, username: username}, - }); + const userData = { + email: email, + username: username, + }; + const json = JSON.stringify(userData); + const formData = new FormData(); + formData.append('json', json); + formData.append('image', image); + const body = formData; + const request = { + headers: { + + }, + credentials: 'include', + body: body, + }; + const path = '/profile'; + const response = await api.put(path, request); + + // const response = await fetch(`${endpoint}/profile`, { + // method: 'PUT', + // headers: { + // 'Content-Type': 'application/json' // Заменено на правильный синтаксис + // }, + // body: JSON.stringify({ email: email, username: username }), // Используйте 'body' вместо 'json' + // credentials: 'include', + // }); if (response.ok) { alert('Profile updated successfully!'); diff --git a/public/modules/FrontendAPI.js b/public/modules/FrontendAPI.js index 4beca4e..f9658b4 100644 --- a/public/modules/FrontendAPI.js +++ b/public/modules/FrontendAPI.js @@ -8,20 +8,16 @@ class FrontendAPI { get(path, request) { request['method'] = 'GET'; return this._commonFetchRequest(path, request); - /* - return this._commonFetchRequest({ - method: 'GET', - path, - headers, - needCredentials, - id, - });*/ } - post(path, request) { - request['method'] = 'POST'; - return this._commonFetchRequest(path, request); - } + post(path, request) { + request['method'] = 'POST'; + return this._commonFetchRequest(path, request); + } + put(path, request) { + request['method'] = 'PUT'; + return this._commonFetchRequest(path, request); + } _removeNullUndefined(obj) { for (const key in obj) { From 16bed76abe43de4cee3d39c6c7ccfce7a920bafd Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Fri, 8 Nov 2024 01:54:04 +0300 Subject: [PATCH 21/35] ADD: delete method --- public/modules/FrontendAPI.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/public/modules/FrontendAPI.js b/public/modules/FrontendAPI.js index f9658b4..786ae60 100644 --- a/public/modules/FrontendAPI.js +++ b/public/modules/FrontendAPI.js @@ -18,6 +18,15 @@ class FrontendAPI { request['method'] = 'PUT'; return this._commonFetchRequest(path, request); } + put(path, request) { + request['method'] = 'PUT'; + return this._commonFetchRequest(path, request); + } + + delete(path, request) { + request['method'] = 'DELETE'; + return this._commonFetchRequest(path, request); + } _removeNullUndefined(obj) { for (const key in obj) { From b5122a06d619be601bd366740b91c255aff10f27 Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Fri, 8 Nov 2024 01:55:39 +0300 Subject: [PATCH 22/35] ADD: delete method --- public/modules/FrontendAPI.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/public/modules/FrontendAPI.js b/public/modules/FrontendAPI.js index 786ae60..6655965 100644 --- a/public/modules/FrontendAPI.js +++ b/public/modules/FrontendAPI.js @@ -18,10 +18,6 @@ class FrontendAPI { request['method'] = 'PUT'; return this._commonFetchRequest(path, request); } - put(path, request) { - request['method'] = 'PUT'; - return this._commonFetchRequest(path, request); - } delete(path, request) { request['method'] = 'DELETE'; From 5f99d39e78b31c348b904039a65daebb86fc7f76 Mon Sep 17 00:00:00 2001 From: Rassmagin Alexander Date: Fri, 8 Nov 2024 02:07:25 +0300 Subject: [PATCH 23/35] image direct to endpoint --- public/components/Profile/Profile.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/public/components/Profile/Profile.js b/public/components/Profile/Profile.js index 0260c8a..00bca9a 100644 --- a/public/components/Profile/Profile.js +++ b/public/components/Profile/Profile.js @@ -69,7 +69,7 @@ export class Profile { console.log(profileData); document.getElementById('username').value = profileData.username; document.getElementById('email').value = profileData.email; - profilePicture.src = profileData.profilePictureUrl || '/static/images/default_avatar.png'; + profilePicture.src = endpoint + '/' + profileData.image || '/static/images/default_avatar.png'; } }).catch(error => { console.error('Error fetching profile data:', error); @@ -81,12 +81,15 @@ export class Profile { async uploadProfilePicture(event) { const file = event.target.files[0]; - if (file) { - const formData = new FormData(); - formData.append('image', file); - const body = formData; - document.getElementById('profileImage').value = file; - } + console.log("hey"); + const reader = new FileReader(); + + // Set up the onload event for the FileReader + reader.onload = (e) => { + // Update the src of the profile picture with the uploaded image + document.getElementById('profileImage').src = e.target.result; + }; + reader.readAsDataURL(file); } async fetchProfileData() { From 6ed864bbf4ab4ed5fe203219a0bdec4a7c2d4598 Mon Sep 17 00:00:00 2001 From: Rassmagin Alexander Date: Fri, 8 Nov 2024 02:28:24 +0300 Subject: [PATCH 24/35] Image from backend --- public/components/Header/Header.js | 29 +++++++++++++++++++++++++++- public/components/Profile/Profile.js | 9 ++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/public/components/Header/Header.js b/public/components/Header/Header.js index 07a55f0..7c86a24 100644 --- a/public/components/Header/Header.js +++ b/public/components/Header/Header.js @@ -3,6 +3,7 @@ * @import {string} endpoint - The API endpoint URL */ import { endpoint } from "../../config.js" +import { api } from '../../modules/FrontendAPI.js'; /** * Header module. * @@ -116,7 +117,14 @@ export class Header { const profileLink = document.createElement('a'); profileLink.href = '/profile'; const avatarImage = document.createElement('img'); - avatarImage.src = '/static/images/myavatar.png'; + + this.fetchProfilePic().then(profilePic => { + if (profilePic) { + avatarImage.src = endpoint + '/' + profilePic.image; + } + }) + + //avatarImage.src = '/static/images/myavatar.png'; avatarImage.onerror = function() { this.src = "/static/images/default_avatar.png"; this.style.objectFit = 'fill'; @@ -163,5 +171,24 @@ export class Header { headerElement.appendChild(buttons); return headerElement; } + async fetchProfilePic() { + try { + const request = { + headers: { + + }, + credentials: 'include', + }; + const response = await api.get('/profile', request); + + if (!response.ok) { + throw new Error('Failed to fetch profile data'); + } + + return await response.json(); + } catch (error) { + console.error('Error fetching profile data:', error); + } + } } \ No newline at end of file diff --git a/public/components/Profile/Profile.js b/public/components/Profile/Profile.js index 00bca9a..5eefeee 100644 --- a/public/components/Profile/Profile.js +++ b/public/components/Profile/Profile.js @@ -94,10 +94,13 @@ export class Profile { async fetchProfileData() { try { - const response = await fetch(`${endpoint}/profile`, { - method: 'GET', + const request = { + headers: { + + }, credentials: 'include', - }); + }; + const response = await api.get('/profile', request); if (!response.ok) { throw new Error('Failed to fetch profile data'); From d0c99ebfe352e000828d4b3dcb2bef5de6cf102b Mon Sep 17 00:00:00 2001 From: Ksu Date: Fri, 8 Nov 2024 04:41:27 +0300 Subject: [PATCH 25/35] categories and event page --- .gitignore | 3 +- .../EventContentPage/EventContentPage.css | 160 ++++++++++--- .../EventContentPage/EventContentPage.js | 136 ++++++----- .../EventCreateForm/EventCreateForm.css | 3 +- .../EventCreateForm/EventCreateForm.js | 2 +- .../EventCreateForm.precompiled.js | 18 +- public/components/Feed/Feed.js | 5 +- public/components/Header/Header.css | 3 +- public/components/Header/Header.js | 2 +- public/components/Nav/Nav.css | 2 +- public/components/Nav/Nav.js | 223 +++++------------- public/config.js | 2 +- public/index.css | 11 +- public/index.js | 53 +++-- public/modules/router.js | 1 + 15 files changed, 312 insertions(+), 312 deletions(-) diff --git a/.gitignore b/.gitignore index 30bc162..ae9094c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/node_modules \ No newline at end of file +/node_modules +*.precompiled.js diff --git a/public/components/EventContentPage/EventContentPage.css b/public/components/EventContentPage/EventContentPage.css index 61a81f2..7c8e503 100644 --- a/public/components/EventContentPage/EventContentPage.css +++ b/public/components/EventContentPage/EventContentPage.css @@ -1,65 +1,147 @@ +/* Основной контейнер для страницы мероприятия */ .eventPage { display: flex; - border: 1px solid #ccc; - padding: 10px; - margin: 10px; + flex-direction: column; + align-items: center; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); + padding: 20px; + margin: 20px auto; + max-width: 70%; + background-color: #fff; + padding: 50px; + font-size: large; } -.event__leftPart { - flex: 1; - display: flex; - flex-direction: column; - padding-right: 10px; +.event__leftPart__title { + font-size: 36px; + font-weight: bold; + color: #333; + margin-bottom: 10px; + text-align: center; + } +.image { + max-width: 80%; + height: auto; + border-radius: 8px; + margin-top: 10px; + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); +} + +/* Правая часть с датами, тегами и кнопками */ .event__rightPart { - display: block; - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-start; + width: 100%; + display: inline-block; + align-items: center; + padding-left: 15px; + padding-top: 15px; +} +.event__date { + padding: 5px; + font-size: 18px; + color: #666; + margin: 8px 0; } -.event__leftPart__title { - font-size: 20px; - font-weight: bold; +.event__tags { + display: flex; + flex-wrap: wrap; + gap: 10px; } -.event__date { - margin: 5px 0; +.event__tag { + background-color: #009951; + color: white; + font-size: 20px; + padding: 7px; + border-radius: 15px; } + +/* Блок для кнопок */ .event_actionsDiv { - display:inline + margin-top: 15px; + display:inline-block; + margin-top: 15px; } -.event_tagsDiv { - display:inline +/* Кнопка удаления */ +.buttonDelete { + background: #fff; + border: 2px solid #e74c3c; + color: #e74c3c; + padding: 10px 20px; + border-radius: 20px; + font-size: 16px; + cursor: pointer; + transition: background-color 0.2s ease, color 0.2s ease; } -.image { - max-width: 40%; +.buttonDelete:hover { + background-color: #e74c3c; + color: #fff; +} + +/* Кнопка редактирования */ +.buttonEdit { + background: #fff; + border: 2px solid #ff7b00; + color: #ff7b00; + padding: 10px 20px; + border-radius: 20px; + font-size: 16px; + cursor: pointer; + transition: background-color 0.2s ease, color 0.2s ease; +} + +.buttonEdit:hover { + background-color: #ff7b00; + color: #fff; +} + +.eventPage { + display: flex; + flex-direction: column; + align-items: center; +} + +.event__title { + font-size: 36px; + font-weight: bold; +} + +.event__details { + display: flex; + flex-direction: column; + align-items: center; +} + +.event__image { + max-width: 800px; height: auto; + margin-top: 10px; } -.buttonDelete { - background: white; - border: 2px solid red; - color:red; +.event__info-row { + display: flex; + flex-direction: column; + justify-content: space-between; + width: 100%; + padding: 10px 0; } -.buttonDelete:active { - background: red; - color: white; + +.event__tags, .event__date { + margin: 0 10px; } -.buttonEdit{ - background: white; - border: 2px solid rgb(255, 123, 0); - color:rgb(255, 123, 0); +.event__actions { + display: flex; + justify-content: center; + margin-top: 30px; + gap: 350px; } -.buttonEdit:active { - background: rgb(255, 123, 0); - color: white; -} \ No newline at end of file + diff --git a/public/components/EventContentPage/EventContentPage.js b/public/components/EventContentPage/EventContentPage.js index 84fc373..2764e42 100644 --- a/public/components/EventContentPage/EventContentPage.js +++ b/public/components/EventContentPage/EventContentPage.js @@ -4,9 +4,13 @@ export class EventContentPage { constructor(eventId) { this.contentBody = document.createElement('div'); this.contentBody.className = 'eventPage'; - this.eventId = eventId; } + _formatDate(dateString) { + const options = { year: 'numeric', month: 'long', day: 'numeric' }; + return new Date(dateString).toLocaleDateString('ru-RU', options); + } + config = { author: { text: '', @@ -44,90 +48,92 @@ export class EventContentPage { className: '', src: '', }, - }; - _renderEvent(event){ + _renderEvent(event) { console.log(event); - console.log(event.title); - - const leftDiv = document.createElement('div'); - leftDiv.className = 'event__leftPart'; - - const title = document.createElement('div'); - title.className = 'event__leftPart__title '; - title.textContent = event.title; - - const image = document.createElement('img'); - image.className = 'image'; - image.src = this.config.image.src; - image.onerror = function() { + + const eventDetails = document.createElement('div'); + eventDetails.className = 'event__details'; + + const eventTitle = document.createElement('div'); + eventTitle.className = 'event__title'; + eventTitle.textContent = event.title; + + const eventImage = document.createElement('img'); + eventImage.className = 'event__image'; + eventImage.src = this.config.image.src; + eventImage.onerror = function () { this.src = "/static/images/placeholder.png"; this.style.objectFit = 'fill'; }; - const descr = document.createElement('div'); /** render event */ - //descr.className = 'title'; - descr.textContent = event.description; - - leftDiv.appendChild(title); - leftDiv.appendChild(image); - leftDiv.appendChild(descr); + const tagsDiv = document.createElement('div'); + tagsDiv.className = 'event__tags'; + event.tag.forEach(tag => { + const tagElement = document.createElement('span'); + tagElement.className = 'event__tag'; + tagElement.textContent = tag; + tagsDiv.appendChild(tagElement); + }); - const rightDiv = document.createElement('div'); - rightDiv.className = 'event__rightPart'; + const eventStartDate = document.createElement('div'); + eventStartDate.className = 'event__date'; + eventStartDate.textContent = `Дата начала: ${this._formatDate(event.event_start)}`; - const actionsDiv = document.createElement('div'); - rightDiv.className = 'event_actionsDiv'; + const eventEndDate = document.createElement('div'); + eventEndDate.className = 'event__date'; + eventEndDate.textContent = `Дата окончания: ${this._formatDate(event.event_end)}`; - const btnDeleteEvent = document.createElement('button'); - btnDeleteEvent.className = 'buttonDelete'; - btnDeleteEvent.textContent = 'Удалить мероприятие'; - const btnEditEvent = document.createElement('button'); - btnEditEvent.className = 'buttonEdit'; - btnEditEvent.textContent = 'Редактировать мероприятие'; - - actionsDiv.appendChild(btnEditEvent); - actionsDiv.appendChild(btnDeleteEvent); + const eventInfoRow = document.createElement('div'); + eventInfoRow.className = 'event__info-row'; + eventInfoRow.appendChild(tagsDiv); + eventInfoRow.appendChild(eventStartDate); + eventInfoRow.appendChild(eventEndDate); - const tagsDiv = document.createElement('div'); - tagsDiv.className = 'event_tagsDiv'; - tagsDiv.textContent = event.tag[0]; - - const startDate = document.createElement('div'); - startDate.className = 'event__date'; - startDate.textContent = 'Дата начала: ' + event.date_start; // Укажите вашу дату начала - - const endDate = document.createElement('div'); - endDate.className = 'event__date'; - endDate.textContent = 'Дата окончания: ' + event.date_end; // Укажите вашу дату окончания - - rightDiv.appendChild(actionsDiv); - rightDiv.appendChild(tagsDiv); - rightDiv.appendChild(startDate); - rightDiv.appendChild(endDate); - - // Добавляем левую и правую части в основной контейнер - this.contentBody.appendChild(leftDiv); - this.contentBody.appendChild(rightDiv); - + const eventDescription = document.createElement('div'); + eventDescription.className = 'event__description'; + eventDescription.textContent = event.description; + + eventDetails.appendChild(eventTitle); + eventDetails.appendChild(eventImage); + eventDetails.appendChild(eventInfoRow); + eventDetails.appendChild(eventDescription); + + const eventActions = document.createElement('div'); + eventActions.className = 'event__actions'; + + const deleteButton = document.createElement('button'); + deleteButton.className = 'buttonDelete'; + deleteButton.textContent = 'Удалить мероприятие'; + + const editButton = document.createElement('button'); + editButton.className = 'buttonEdit'; + editButton.textContent = 'Редактировать мероприятие'; + + eventActions.appendChild(editButton); + eventActions.appendChild(deleteButton); + + this.contentBody.appendChild(eventDetails); + this.contentBody.appendChild(eventActions); } + async renderTemplate(id) { console.log(id); - const path = '/events/'+id.toString(); - const request = {headers: {}}; + const path = `/events/${id}`; + const request = { headers: {} }; + try { const response = await api.get(path, request); - const event = await response.json(); this._renderEvent(event); - + } catch (error) { console.log(error); - console.log("ERROR HERE"); - /* some more useful error handling */ + console.log("Ошибка при загрузке события"); } - return this.contentBody; + + return this.contentBody; } -} \ No newline at end of file +} diff --git a/public/components/EventCreateForm/EventCreateForm.css b/public/components/EventCreateForm/EventCreateForm.css index dbd336d..bc351ff 100644 --- a/public/components/EventCreateForm/EventCreateForm.css +++ b/public/components/EventCreateForm/EventCreateForm.css @@ -1,4 +1,5 @@ .edit_container { display: flex; align-items: center; -} \ No newline at end of file +} + \ No newline at end of file diff --git a/public/components/EventCreateForm/EventCreateForm.js b/public/components/EventCreateForm/EventCreateForm.js index 310f23e..cee5ad8 100644 --- a/public/components/EventCreateForm/EventCreateForm.js +++ b/public/components/EventCreateForm/EventCreateForm.js @@ -92,7 +92,7 @@ export class EventCreateForm { eventTagsEntry: { - text: 'Тэги (не более 3 штуки)', + text: 'Тэги (не более 3 штук)', tag: 'input', diff --git a/public/components/EventCreateForm/EventCreateForm.precompiled.js b/public/components/EventCreateForm/EventCreateForm.precompiled.js index 1bfa167..691667b 100644 --- a/public/components/EventCreateForm/EventCreateForm.precompiled.js +++ b/public/components/EventCreateForm/EventCreateForm.precompiled.js @@ -8,7 +8,9 @@ templates['EventCreateForm.hbs'] = template({"1":function(container,depth0,helpe return undefined }; - return ((stack1 = lookupProperty(helpers,"if").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"needPlaceholder") : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(4, data, 0),"data":data,"loc":{"start":{"line":2,"column":4},"end":{"line":6,"column":11}}})) != null ? stack1 : ""); + return " \n" + + ((stack1 = lookupProperty(helpers,"if").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"needPlaceholder") : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(4, data, 0),"data":data,"loc":{"start":{"line":3,"column":8},"end":{"line":11,"column":15}}})) != null ? stack1 : "") + + "\n"; },"2":function(container,depth0,helpers,partials,data) { var alias1=container.lambda, alias2=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) { if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { @@ -17,7 +19,9 @@ templates['EventCreateForm.hbs'] = template({"1":function(container,depth0,helpe return undefined }; - return " <" + return "
\n \n <" + alias2(alias1((depth0 != null ? lookupProperty(depth0,"tag") : depth0), depth0)) + " class=\"" + alias2(alias1((depth0 != null ? lookupProperty(depth0,"className") : depth0), depth0)) @@ -31,7 +35,7 @@ templates['EventCreateForm.hbs'] = template({"1":function(container,depth0,helpe + alias2(alias1((depth0 != null ? lookupProperty(depth0,"key") : depth0), depth0)) + "\">\n"; + + ">\n
\n \n"; },"4":function(container,depth0,helpers,partials,data) { var alias1=container.lambda, alias2=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) { if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { @@ -40,13 +44,15 @@ templates['EventCreateForm.hbs'] = template({"1":function(container,depth0,helpe return undefined }; - return " <" + return " <" + alias2(alias1((depth0 != null ? lookupProperty(depth0,"tag") : depth0), depth0)) + " class=\"" + alias2(alias1((depth0 != null ? lookupProperty(depth0,"className") : depth0), depth0)) + "\" id = \"" + alias2(alias1((depth0 != null ? lookupProperty(depth0,"key") : depth0), depth0)) - + "\" >" + + "\" data-section=\"" + + alias2(alias1((depth0 != null ? lookupProperty(depth0,"key") : depth0), depth0)) + + "\">" + alias2(alias1((depth0 != null ? lookupProperty(depth0,"text") : depth0), depth0)) + " { event.preventDefault(); - const path = '/my_events'; + const path = '/events/my'; navigate(path); }); diff --git a/public/components/Nav/Nav.css b/public/components/Nav/Nav.css index 6522fa6..907153c 100644 --- a/public/components/Nav/Nav.css +++ b/public/components/Nav/Nav.css @@ -4,7 +4,7 @@ nav { height: 70px; letter-spacing: 1px; font-size: 18px; - padding: 0 100px 0 50px; + padding: 0 100px 0 90px; display: flex; justify-content: center; align-items: center; diff --git a/public/components/Nav/Nav.js b/public/components/Nav/Nav.js index af0853e..cab97e0 100644 --- a/public/components/Nav/Nav.js +++ b/public/components/Nav/Nav.js @@ -1,172 +1,59 @@ -/** - * Nav class +import { api } from "../../modules/FrontendAPI.js"; +import { navigate } from "../../modules/router.js"; +export class Nav { + + constructor() { + this.navElement = document.createElement('nav'); + this.navigate = navigate; + } + /** + * Renders the navigation template + * @returns {HTMLNavElement} The rendered navigation */ -export class Nav { - /** - * Creates a new Nav instance - */ - constructor() { - /** - * The nav element - * @type {HTMLNavElement} - */ - this.navElement = document.createElement('nav'); - /** - * The ul element - * @type {HTMLUListElement} - */ - this.ul = document.createElement('ul'); - } - - /** - * Navigation configuration object - * @type {Object} - */ - navigation = { - /** - * Popular navigation item configuration - * @type {Object} - */ - Popular: { - /** - * Link href - * @type {string} - */ - href: '/events', - /** - * Link text - * @type {string} - */ - text: 'Все события', - }, - /** - * Exhibition navigation item configuration - * @type {Object} - */ - Exhibition: { - /** - * Link href - * @type {string} - */ - href: '/exhibitions', - /** - * Link text - * @type {string} - */ - text: 'Выставки', - }, - /** - * Theater navigation item configuration - * @type {Object} - */ - Theater: { - /** - * Link href - * @type {string} - */ - href: '/theater', - /** - * Link text - * @type {string} - */ - text: 'Театр', - }, - /** - * Cinema navigation item configuration - * @type {Object} - */ - Cinema: { - /** - * Link href - * @type {string} - */ - href: '/cinema', - /** - * Link text - * @type {string} - */ - text: 'Кино', - }, - /** - * Food navigation item configuration - * @type {Object} - */ - Food: { - /** - * Link href - * @type {string} - */ - href: '/food', - /** - * Link text - * @type {string} - */ - text: 'Еда', - }, - /** - * Kids navigation item configuration - * @type {Object} - */ - Kids: { - /** - * Link href - * @type {string} - */ - href: '/kids', - /** - * Link text - * @type {string} - */ - text: 'Детям', - }, - /** - * Past navigation item configuration - * @type {Object} - */ - Past: { - /** - * Link href - * @type {string} - */ - href: '/past', - /** - * Link text - * @type {string} - */ - text: 'Прошедшие', - }, - /** - * Sport navigation item configuration - * @type {Object} - */ - Sport: { - /** - * Link href - * @type {string} - */ - href: '/sport', - /** - * Link text - * @type {string} - */ - text: 'Спорт', - }, +async renderNav() { + try { + + const request = { headers: {} }; + const response = await api.get('/categories', request); + const dynamicCategories = await response.json(); + + console.log(dynamicCategories); + + const allEventsItem = { + key: 'allEvents', + href: '/events', + text: 'Все события' + }; + + const pastEventsItem = { + key: 'pastEvents', + href: '/events/past', + text: 'Прошедшие' }; - - /** - * Renders the navigation template - * @returns {HTMLNavElement} The rendered navigation - */ - renderNav() { - const template = Handlebars.templates['Nav.hbs']; - const config = this.navigation; - let itemss = Object.entries(config); - let items = itemss.map(([key, {href, text}], index) => { - return {key, href, text}; + + const dynamicItems = dynamicCategories.map(category => ({ + key: category.id || category.name, + href: `/events/categories/${category.id}`, + text: category.name + })); + + const items = [allEventsItem, ...dynamicItems, pastEventsItem]; + + const template = Handlebars.templates['Nav.hbs']; + this.navElement.innerHTML += template({ items }); + + this.navElement.querySelectorAll('a').forEach(link => { + link.addEventListener('click', (event) => { + event.preventDefault(); + const path = link.getAttribute('href'); + this.navigate(path); }); - - this.navElement.innerHTML += template({items}); - return this.navElement; - } + }); + + return this.navElement; + } catch (error) { + console.error('Нет категорий:', error); + return this.navElement; } - \ No newline at end of file +} +} diff --git a/public/config.js b/public/config.js index f6c7ec6..d32c7c7 100644 --- a/public/config.js +++ b/public/config.js @@ -1 +1 @@ -export const endpoint = "http://37.139.40.252:8080"; +export const endpoint = "http://127.0.0.1:8080"; diff --git a/public/index.css b/public/index.css index c39c002..ca1b5ff 100644 --- a/public/index.css +++ b/public/index.css @@ -4,6 +4,13 @@ @import "components/Footer/Footer.css"; @import "components/EventContentPage/EventContentPage.css"; @import "components/UserEventsPage/UserEventsPage.css"; + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + UserEventsPage body { font-family: Arial, sans-serif; @@ -34,7 +41,7 @@ body { top: 50%; left: 50%; transform: translate(-50%, -50%); - background: #fff; + background: #f1f1f1; padding: 20px; border: 1px solid #ddd; border-radius: 10px; @@ -83,8 +90,6 @@ body { margin: 20px; } - - .feed-element img { width: 100%; height: 300px; diff --git a/public/index.js b/public/index.js index 1e8eed7..5daae7a 100644 --- a/public/index.js +++ b/public/index.js @@ -94,29 +94,24 @@ function updateLinksContainer() { header = newHeaderElement; } -/** - * Create the initial header element - */ -let header = new Header().renderHeader(userIsLoggedIn, logout, navigate); -root.appendChild(header); +const newsFeed = document.createElement('main'); -/** - * Create the navigation element - */ -const nav = new Nav().renderNav(); -root.appendChild(nav); +async function initializeApp() { + // Добавление header + let header = new Header().renderHeader(userIsLoggedIn, logout, navigate); + root.appendChild(header); -/** - * Create the feed element - */ -const newsFeed = document.createElement('main'); -root.appendChild(newsFeed); + // Добавление навигации + const nav = await new Nav().renderNav(); + root.appendChild(nav); -/** - * Create the footer element - */ -const footer = new Footer().renderFooter(); -root.appendChild(footer); + root.appendChild(newsFeed); + + const footer = new Footer().renderFooter(); + root.appendChild(footer); +} + +initializeApp(); /** * Create the response element @@ -162,18 +157,32 @@ const routes = { */ '/events': async() => { newsFeed.innerHTML = ''; // Clear the modal window content - let feed = await new Feed().renderFeed(); + let feed = await new Feed().renderFeed('/events'); newsFeed.appendChild(feed); }, '/events/:id': async(id) => { newsFeed.innerHTML = ''; // Clear the modal window content + let eventPage = await new EventContentPage('event').renderTemplate(id); newsFeed.appendChild(eventPage); }, - '/my_events': async(id) => { + '/events/my': async(id) => { newsFeed.innerHTML = ''; // Clear the modal window content let UserEventPage = await new UserEventsPage('userEvents').renderTemplate(id); newsFeed.appendChild(UserEventPage); + let eventPage = await new Feed().renderFeed('/events/my'); + newsFeed.appendChild(eventPage); + }, + '/events/categories/:id': async(id) => { + newsFeed.innerHTML = ''; // Clear the modal window content + console.log("cat") + let eventPage = await new Feed().renderFeed(`/events/categories/${id}`); + newsFeed.appendChild(eventPage); + }, + '/events/past': async() => { + newsFeed.innerHTML = ''; // Clear the modal window content + let eventPage = await new Feed().renderFeed('/events/past'); + newsFeed.appendChild(eventPage); }, '/add_event': async() => { newsFeed.innerHTML = ''; // Clear the modal window content diff --git a/public/modules/router.js b/public/modules/router.js index 5b36947..e8f53de 100644 --- a/public/modules/router.js +++ b/public/modules/router.js @@ -6,5 +6,6 @@ export const navigate = (path) => { /** * Dispatch a popstate event */ + console.log(path) window.dispatchEvent(new PopStateEvent('popstate')); }; From 35a5f32e0a61ec0ba16c036d90a53ce3396c4a36 Mon Sep 17 00:00:00 2001 From: Rassmagin Alexander Date: Fri, 8 Nov 2024 08:07:29 +0300 Subject: [PATCH 26/35] Navigatable feed elements --- public/components/Feed/Feed.js | 9 ++++++--- public/index.js | 11 ++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/public/components/Feed/Feed.js b/public/components/Feed/Feed.js index e11d757..27e2276 100644 --- a/public/components/Feed/Feed.js +++ b/public/components/Feed/Feed.js @@ -3,6 +3,7 @@ * @import {string} endpoint - The API endpoint URL */ import { endpoint } from "../../config.js" +import { navigate } from "../../modules/router.js"; /** * Import the FeedElement component from the FeedElement.js file * @import FeedElement - A component representing a feed element @@ -53,7 +54,6 @@ export class Feed { * * @type {Response} */ - console.log(`${endpoint}${apiPath}`) const response = await fetch(`${endpoint}${apiPath}`, { /** * The HTTP method for the request. @@ -87,12 +87,15 @@ export class Feed { * @param {string} description - The description of the event. * @param {string} image - The image URL of the event. */ - console.log(feed.events); Object.entries(feed.events).forEach( (elem) => { const {id, description, image} = elem[1]; const feedElement = new FeedElement(id, description, `${endpoint}${image}`).renderTemplate(); feedContent.appendChild(feedElement); - //console.log(elem[1]); + feedElement.addEventListener('click', (event) => { + event.preventDefault(); + const path = `/events/${id}`; + navigate(path); + }); }); } else { diff --git a/public/index.js b/public/index.js index 12742b0..72016b8 100644 --- a/public/index.js +++ b/public/index.js @@ -183,7 +183,6 @@ const routes = { }, '/events/categories/:id': async(id) => { newsFeed.innerHTML = ''; // Clear the modal window content - console.log("cat") let eventPage = await new Feed().renderFeed(`/events/categories/${id}`); newsFeed.appendChild(eventPage); }, @@ -230,7 +229,14 @@ const defaultRoute = () => { window.addEventListener('popstate', () => { const path = window.location.pathname; const route = routes[path]; - if (route) { + if (/\/events\/\d+/.test(path)) { + /** + * Call the events route function + */ + const id = path.split('/')[2]; + routes['/events/:id'](id); + } + else if (route) { route(); } else { defaultRoute(); // Call the default route if no matching route is found @@ -241,7 +247,6 @@ window.addEventListener('popstate', () => { * Check the current path when the page is loaded */ const currentPath = window.location.pathname; -console.log(currentPath); /** * Check if the current path is the login or signup page */ From 5d2615734480112df9938fb2785b29d3c075ffc6 Mon Sep 17 00:00:00 2001 From: Rassmagin Alexander Date: Fri, 8 Nov 2024 08:18:43 +0300 Subject: [PATCH 27/35] images from back --- public/components/EventContentPage/EventContentPage.js | 4 +++- public/components/Feed/Feed.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/components/EventContentPage/EventContentPage.js b/public/components/EventContentPage/EventContentPage.js index 2764e42..c00665b 100644 --- a/public/components/EventContentPage/EventContentPage.js +++ b/public/components/EventContentPage/EventContentPage.js @@ -1,4 +1,5 @@ import { api } from "../../modules/FrontendAPI.js"; +import { endpoint } from "../../config.js"; export class EventContentPage { constructor(eventId) { @@ -62,7 +63,8 @@ export class EventContentPage { const eventImage = document.createElement('img'); eventImage.className = 'event__image'; - eventImage.src = this.config.image.src; + eventImage.src = endpoint + '/' + event.image; + console.log(eventImage.src); eventImage.onerror = function () { this.src = "/static/images/placeholder.png"; this.style.objectFit = 'fill'; diff --git a/public/components/Feed/Feed.js b/public/components/Feed/Feed.js index 27e2276..c37db96 100644 --- a/public/components/Feed/Feed.js +++ b/public/components/Feed/Feed.js @@ -89,7 +89,7 @@ export class Feed { */ Object.entries(feed.events).forEach( (elem) => { const {id, description, image} = elem[1]; - const feedElement = new FeedElement(id, description, `${endpoint}${image}`).renderTemplate(); + const feedElement = new FeedElement(id, description, `${endpoint}/${image}`).renderTemplate(); feedContent.appendChild(feedElement); feedElement.addEventListener('click', (event) => { event.preventDefault(); From a62b71c4431180a790c54147104ed3c38e04aed7 Mon Sep 17 00:00:00 2001 From: Rassmagin Alexander Date: Fri, 8 Nov 2024 08:19:19 +0300 Subject: [PATCH 28/35] comment + package --- package-lock.json | 31 +++++++++++++++---------------- package.json | 2 +- public/modules/router.js | 1 - 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c822d6..f9552b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "cors": "^2.8.5", "dompurify": "^3.1.7", - "express": "^4.21.0", + "express": "^4.21.1", "handlebars": "^4.7.8", "minimist": "^1.2.8", "node": "^22.9.0" @@ -542,10 +542,9 @@ } }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "license": "MIT", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -912,16 +911,16 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -3131,9 +3130,9 @@ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" }, "cookie-signature": { "version": "1.0.6", @@ -3377,16 +3376,16 @@ } }, "express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", diff --git a/package.json b/package.json index 62b0252..2503c6d 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "dependencies": { "cors": "^2.8.5", "dompurify": "^3.1.7", - "express": "^4.21.0", + "express": "^4.21.1", "handlebars": "^4.7.8", "minimist": "^1.2.8", "node": "^22.9.0" diff --git a/public/modules/router.js b/public/modules/router.js index e8f53de..5b36947 100644 --- a/public/modules/router.js +++ b/public/modules/router.js @@ -6,6 +6,5 @@ export const navigate = (path) => { /** * Dispatch a popstate event */ - console.log(path) window.dispatchEvent(new PopStateEvent('popstate')); }; From da0531e87b13e66297184c938cd24556b9ac10e9 Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Fri, 8 Nov 2024 08:52:19 +0300 Subject: [PATCH 29/35] ADD: delete action for event --- public/modules/handleEventsActions.js | 126 +++++++------------------- 1 file changed, 33 insertions(+), 93 deletions(-) diff --git a/public/modules/handleEventsActions.js b/public/modules/handleEventsActions.js index 043be4b..5c5983a 100644 --- a/public/modules/handleEventsActions.js +++ b/public/modules/handleEventsActions.js @@ -13,6 +13,7 @@ * @import {function} removeDangerous - Removes dangerous characters from a string */ import { isValidUsername, isValidPassword, isValidEmail, removeDangerous } from './FormValidation.js'; +import { navigate } from "../../modules/router.js"; /** * Import the endpoint configuration from the config.js file * @import {string} endpoint - The API endpoint URL @@ -20,7 +21,7 @@ import { isValidUsername, isValidPassword, isValidEmail, removeDangerous } from import { endpoint } from "../../config.js" import { api } from './FrontendAPI.js'; -console.log(api); + /** * Error message for empty fields. * @constant {string} @@ -62,7 +63,7 @@ export async function loadCategories() { console.log(selectElement); try { const request = { headers: {} }; - console.log(api); + const response = await api.get('/categories', request); const categories = await response.json(); @@ -82,61 +83,41 @@ export async function loadCategories() { return selectElement; } -export async function handleCreateEventSubmit(event, pageToCome, navigate) { +export async function handleDeleteEventSubmit(event, id, pageToCome) { event.preventDefault(); - loadCategories(); - // Get form data - const title = removeDangerous(document.getElementById('eventNameEntry').value); - const description = removeDangerous(document.getElementById('eventDescriptionEntry').value); - const tags = removeDangerous(document.getElementById('eventTagsEntry').value).split(); - const dateStart = removeDangerous(document.getElementById('eventBeginEntry').value) + ':00Z'; - const dateEnd = removeDangerous(document.getElementById('eventEndEntry').value) + ':00Z'; - - const categoryId = Number(removeDangerous(document.getElementById('categoriesInput').value)); - - const image = document.getElementById('imageInput').files[0]; - console.log(title, description, tags, dateStart, dateEnd, image, categoryId); - /* - // Clear error messages - document.getElementById('registerUsernameError').innerText = ''; - document.getElementById('registerPasswordError').innerText = ''; - document.getElementById('registerEmailError').innerText = ''; - document.getElementById('registerServerError').innerText = ''; - - // Get form data - const username = removeDangerous(document.getElementById('registerUsernameEntry').value); - const email = removeDangerous(document.getElementById('registerEmailEntry').value); - const password = removeDangerous(document.getElementById('registerPasswordEntry').value); - - // Initialize validation flag - let isValid = true; - - // Validate form data - if (!username) { - document.getElementById('registerUsernameError').innerText = EMPTY_FIELD; - isValid = false; - } - if (!isValidUsername(username)) { - document.getElementById('registerUsernameError').innerText = INCORRECT_USERNAME; - isValid = false; - } + try { + // Send request to backend - if (!isValidEmail(email)) { - document.getElementById('registerEmailError').innerText = INCORRECT_EMAIL; - isValid = false; - } + const request = { + headers: { + + }, + credentials: 'include', + }; + console.log('ID', id); + const path = '/events'+'/'+id; + const response = await api.delete(path, request); + // If response is not OK, throw error + if (!response.ok) { + throw new Error(data.message); + } + + // Navigate to page + navigate(pageToCome); - if (!isValidPassword(password)) { - document.getElementById('registerPasswordError').innerText = INCORRECT_PASSWORD; - isValid = false; - } + } catch (error) { + // Display error message if registration fails + document.getElementById('eventServerError').innerText = error; + } + //navigate(pageToCome); //debug +} - // If form data is invalid, exit function - if (!isValid) { - return; - } - */ +export async function handleCreateEventSubmit(event, pageToCome, navigate) { + event.preventDefault(); + + + try { // Send request to backend const userData = { @@ -180,44 +161,3 @@ export async function handleCreateEventSubmit(event, pageToCome, navigate) { //navigate(pageToCome); //debug } -/** - * Handles the registration form on keyup input validation. - * - * This function dynamically checks the input fields for validity and displays error messages accordingly. - * - * @function handleRegisterCheck - * @param {Event} event - The input event. - */ -export function handleCreateEventCheck(event) { - const target = event.target; - const id = target.id; - - if (id === 'registerUsernameEntry') { - const username = removeDangerous(target.value); - if (!username) { - document.getElementById('registerUsernameError').innerText = EMPTY_FIELD; - } else if (!isValidUsername(username)) { - document.getElementById('registerUsernameError').innerText = INCORRECT_USERNAME; - } else { - document.getElementById('registerUsernameError').innerText = ''; - } - } else if (id === 'registerEmailEntry') { - const email = removeDangerous(target.value); - if (!email) { - document.getElementById('registerEmailError').innerText = EMPTY_FIELD; - } else if (!isValidEmail(email)) { - document.getElementById('registerEmailError').innerText = INCORRECT_EMAIL; - } else { - document.getElementById('registerEmailError').innerText = ''; - } - } else if (id === 'registerPasswordEntry') { - const password = removeDangerous(target.value); - if (!password) { - document.getElementById('registerPasswordError').innerText = EMPTY_FIELD; - } else if (!isValidPassword(password)) { - document.getElementById('registerPasswordError').innerText = INCORRECT_PASSWORD; - } else { - document.getElementById('registerPasswordError').innerText = ''; - } - } -} From 138a108a18d2fb3eef9deaf8c0725111974c1bdc Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Fri, 8 Nov 2024 08:54:34 +0300 Subject: [PATCH 30/35] fixed blank lines in handleEventsActions.js --- public/modules/handleEventsActions.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/public/modules/handleEventsActions.js b/public/modules/handleEventsActions.js index 5c5983a..77420bf 100644 --- a/public/modules/handleEventsActions.js +++ b/public/modules/handleEventsActions.js @@ -115,9 +115,7 @@ export async function handleDeleteEventSubmit(event, id, pageToCome) { export async function handleCreateEventSubmit(event, pageToCome, navigate) { event.preventDefault(); - - - + try { // Send request to backend const userData = { From fd1e47fb29a1846caa84fc328ce6475997f650f7 Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Fri, 8 Nov 2024 11:21:17 +0300 Subject: [PATCH 31/35] ADD: delete action for event --- .../EventContentPage/EventContentPage.js | 160 +++++++++--------- public/modules/handleEventsActions.js | 71 +++++++- 2 files changed, 150 insertions(+), 81 deletions(-) diff --git a/public/components/EventContentPage/EventContentPage.js b/public/components/EventContentPage/EventContentPage.js index 2764e42..fdf8243 100644 --- a/public/components/EventContentPage/EventContentPage.js +++ b/public/components/EventContentPage/EventContentPage.js @@ -1,16 +1,15 @@ import { api } from "../../modules/FrontendAPI.js"; +import { navigate } from "../../modules/router.js"; +import { handleDeleteEventSubmit } from "../../modules/handleEventsActions.js"; +import {endpoint} from "../../config.js" export class EventContentPage { constructor(eventId) { this.contentBody = document.createElement('div'); this.contentBody.className = 'eventPage'; + this.eventId = eventId; } - _formatDate(dateString) { - const options = { year: 'numeric', month: 'long', day: 'numeric' }; - return new Date(dateString).toLocaleDateString('ru-RU', options); - } - config = { author: { text: '', @@ -48,92 +47,93 @@ export class EventContentPage { className: '', src: '', }, + }; - _renderEvent(event) { + _renderEvent(event){ console.log(event); - - const eventDetails = document.createElement('div'); - eventDetails.className = 'event__details'; - - const eventTitle = document.createElement('div'); - eventTitle.className = 'event__title'; - eventTitle.textContent = event.title; - - const eventImage = document.createElement('img'); - eventImage.className = 'event__image'; - eventImage.src = this.config.image.src; - eventImage.onerror = function () { - this.src = "/static/images/placeholder.png"; - this.style.objectFit = 'fill'; - }; - + console.log(event.title); + + const leftDiv = document.createElement('div'); + leftDiv.className = 'event__leftPart'; + + const title = document.createElement('div'); + title.className = 'event__leftPart__title '; + title.textContent = event.title; + + const image = document.createElement('img'); + image.className = 'image'; + image.src = endpoint + '/' + event.image || "/static/images/placeholder.png"; //this.config.image.src; + + const descr = document.createElement('div'); /** render event */ + //descr.className = 'title'; + descr.textContent = event.description; + + leftDiv.appendChild(title); + leftDiv.appendChild(image); + leftDiv.appendChild(descr); + + const rightDiv = document.createElement('div'); + rightDiv.className = 'event__rightPart'; + + const actionsDiv = document.createElement('div'); + rightDiv.className = 'event_actionsDiv'; + + + + this.btnEditEvent = document.createElement('button'); + this.btnEditEvent.className = 'buttonEdit'; + this.btnEditEvent.textContent = 'Редактировать мероприятие'; + + this.btnDeleteEvent = document.createElement('button'); + this.btnDeleteEvent.className = 'buttonDelete'; + this.btnDeleteEvent.textContent = 'Удалить мероприятие'; + + actionsDiv.appendChild(this.btnEditEvent); + actionsDiv.appendChild(this.btnDeleteEvent); + const tagsDiv = document.createElement('div'); - tagsDiv.className = 'event__tags'; - event.tag.forEach(tag => { - const tagElement = document.createElement('span'); - tagElement.className = 'event__tag'; - tagElement.textContent = tag; - tagsDiv.appendChild(tagElement); - }); - - const eventStartDate = document.createElement('div'); - eventStartDate.className = 'event__date'; - eventStartDate.textContent = `Дата начала: ${this._formatDate(event.event_start)}`; - - const eventEndDate = document.createElement('div'); - eventEndDate.className = 'event__date'; - eventEndDate.textContent = `Дата окончания: ${this._formatDate(event.event_end)}`; - - - const eventInfoRow = document.createElement('div'); - eventInfoRow.className = 'event__info-row'; - eventInfoRow.appendChild(tagsDiv); - eventInfoRow.appendChild(eventStartDate); - eventInfoRow.appendChild(eventEndDate); - - const eventDescription = document.createElement('div'); - eventDescription.className = 'event__description'; - eventDescription.textContent = event.description; - - eventDetails.appendChild(eventTitle); - eventDetails.appendChild(eventImage); - eventDetails.appendChild(eventInfoRow); - eventDetails.appendChild(eventDescription); - - const eventActions = document.createElement('div'); - eventActions.className = 'event__actions'; - - const deleteButton = document.createElement('button'); - deleteButton.className = 'buttonDelete'; - deleteButton.textContent = 'Удалить мероприятие'; - - const editButton = document.createElement('button'); - editButton.className = 'buttonEdit'; - editButton.textContent = 'Редактировать мероприятие'; - - eventActions.appendChild(editButton); - eventActions.appendChild(deleteButton); - - this.contentBody.appendChild(eventDetails); - this.contentBody.appendChild(eventActions); + tagsDiv.className = 'event_tagsDiv'; + tagsDiv.textContent = event.tag[0]; + + const startDate = document.createElement('div'); + startDate.className = 'event__date'; + startDate.textContent = 'Дата начала: ' + event.date_start; // + + const endDate = document.createElement('div'); + endDate.className = 'event__date'; + endDate.textContent = 'Дата окончания: ' + event.date_end; // + + rightDiv.appendChild(actionsDiv); + rightDiv.appendChild(tagsDiv); + rightDiv.appendChild(startDate); + rightDiv.appendChild(endDate); + + // Добавляем левую и правую части в основной контейнер + this.contentBody.appendChild(leftDiv); + this.contentBody.appendChild(rightDiv); + } - async renderTemplate(id) { console.log(id); - const path = `/events/${id}`; - const request = { headers: {} }; - + const path = '/events/'+id.toString(); + const request = {headers: {}}; try { const response = await api.get(path, request); - const event = await response.json(); - this._renderEvent(event); + const eventCon = await response.json(); + this._renderEvent(eventCon); + + + console.log(this.btnDeleteEvent); + + this.btnDeleteEvent.addEventListener('click', (event)=>{handleDeleteEventSubmit(event, id, '/events')}); + } catch (error) { console.log(error); - console.log("Ошибка при загрузке события"); + console.log("ERROR HERE"); + /* some more useful error handling */ } - - return this.contentBody; + return this.contentBody; } -} +} \ No newline at end of file diff --git a/public/modules/handleEventsActions.js b/public/modules/handleEventsActions.js index 77420bf..4700b93 100644 --- a/public/modules/handleEventsActions.js +++ b/public/modules/handleEventsActions.js @@ -113,8 +113,20 @@ export async function handleDeleteEventSubmit(event, id, pageToCome) { //navigate(pageToCome); //debug } -export async function handleCreateEventSubmit(event, pageToCome, navigate) { +export async function handleEditEventSubmit(event, pageToCome, navigate) { event.preventDefault(); + + // Get form data + const title = removeDangerous(document.getElementById('eventNameEntry').value); + const description = removeDangerous(document.getElementById('eventDescriptionEntry').value); + const tags = removeDangerous(document.getElementById('eventTagsEntry').value).split(); + const dateStart = removeDangerous(document.getElementById('eventBeginEntry').value) + ':00Z'; + const dateEnd = removeDangerous(document.getElementById('eventEndEntry').value) + ':00Z'; + + const categoryId = Number(removeDangerous(document.getElementById('categoriesInput').value)); + + const image = document.getElementById('imageInput').files[0]; + console.log(title, description, tags, dateStart, dateEnd, image, categoryId); try { // Send request to backend @@ -159,3 +171,60 @@ export async function handleCreateEventSubmit(event, pageToCome, navigate) { //navigate(pageToCome); //debug } +export async function handleCreateEventSubmit(event, pageToCome, navigate) { + event.preventDefault(); + + // Get form data + const title = removeDangerous(document.getElementById('eventNameEntry').value); + const description = removeDangerous(document.getElementById('eventDescriptionEntry').value); + const tags = removeDangerous(document.getElementById('eventTagsEntry').value).split(); + const dateStart = removeDangerous(document.getElementById('eventBeginEntry').value) + ':00Z'; + const dateEnd = removeDangerous(document.getElementById('eventEndEntry').value) + ':00Z'; + + const categoryId = Number(removeDangerous(document.getElementById('categoriesInput').value)); + + const image = document.getElementById('imageInput').files[0]; + console.log(title, description, tags, dateStart, dateEnd, image, categoryId); + + try { + // Send request to backend + const userData = { + title: title, + description: description, + tags: tags, + event_start: dateStart, + event_end: dateEnd, + category_id: categoryId, + }; + + const json = JSON.stringify(userData); + const formData = new FormData(); + formData.append('json', json); + formData.append('image', image); + const body = formData; + const request = { + headers: { + + }, + credentials: 'include', + body: body, + }; + const path = '/events'; + const response = await api.post(path, request); + // If response is not OK, throw error + if (!response.ok) { + throw new Error(data.message); + } + const data = await response.json(); + if (data.code) { + throw new Error(data.message); + } + // Navigate to page + navigate(pageToCome); + + } catch (error) { + // Display error message if registration fails + document.getElementById('eventServerError').innerText = error; + } +} + From 944bb14076c6f94222dac7dd857834baa8275125 Mon Sep 17 00:00:00 2001 From: Rassmagin Alexander Date: Fri, 8 Nov 2024 12:10:54 +0300 Subject: [PATCH 32/35] categ on refresh --- public/components/Nav/Nav.js | 2 -- public/index.js | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/public/components/Nav/Nav.js b/public/components/Nav/Nav.js index cab97e0..55c23e4 100644 --- a/public/components/Nav/Nav.js +++ b/public/components/Nav/Nav.js @@ -17,8 +17,6 @@ async renderNav() { const response = await api.get('/categories', request); const dynamicCategories = await response.json(); - console.log(dynamicCategories); - const allEventsItem = { key: 'allEvents', href: '/events', diff --git a/public/index.js b/public/index.js index 72016b8..f3ae9b5 100644 --- a/public/index.js +++ b/public/index.js @@ -193,7 +193,7 @@ const routes = { }, '/search': async() => { newsFeed.innerHTML = ''; // Clear the modal window content - let feed = await new Search().renderSearch(navigate, window.location.search.substring(1)); + let feed = await new Search().renderSearch('/events', window.location.search.substring(1)); newsFeed.appendChild(feed); }, '/add_event': async() => { @@ -236,6 +236,13 @@ window.addEventListener('popstate', () => { const id = path.split('/')[2]; routes['/events/:id'](id); } + else if (/\/events\/categories\/\d+/.test(path)) { + /** + * Call the events route function + */ + const id = path.split('/')[3]; + routes['/events/categories/:id'](id); + } else if (route) { route(); } else { @@ -272,12 +279,18 @@ if (currentPath === '/login' || currentPath === '/signup' || currentPath == '/pr */ const id = currentPath.split('/')[2]; routes['/events/:id'](id); // Вызываем обработчик с id +} else if (/\/events\/categories\/\d+/.test(currentPath)) { + /** + * Call the events route function + */ + const id = currentPath.split('/')[3]; + routes['/events/categories/:id'](id); } else if (currentPath === '/my_events') { //(/\/events\d+/.test(currentPath)) //let num = currentPath.match(/\d+/)[0]; const num = 0; /* somehow get current user id and check that user is logged in*/ - routes['/my_events'](num); + routes['/events/my'](num); } else if (currentPath === '/add_event') { routes['/add_event'](); } else { From 3ffa657375cde85147bcd7f1148b5cd51575822a Mon Sep 17 00:00:00 2001 From: Rassmagin Alexander Date: Fri, 8 Nov 2024 13:45:36 +0300 Subject: [PATCH 33/35] search --- .../EventContentPage/EventContentPage.js | 17 ++- public/components/Feed/Feed.js | 1 + public/components/Search/Search.js | 132 ++++++++++++------ public/index.js | 33 ++++- 4 files changed, 130 insertions(+), 53 deletions(-) diff --git a/public/components/EventContentPage/EventContentPage.js b/public/components/EventContentPage/EventContentPage.js index c00665b..0bd8ecb 100644 --- a/public/components/EventContentPage/EventContentPage.js +++ b/public/components/EventContentPage/EventContentPage.js @@ -1,5 +1,6 @@ import { api } from "../../modules/FrontendAPI.js"; import { endpoint } from "../../config.js"; +import { navigate } from "../../modules/router.js"; export class EventContentPage { constructor(eventId) { @@ -52,7 +53,7 @@ export class EventContentPage { }; _renderEvent(event) { - console.log(event); + //console.log(event); const eventDetails = document.createElement('div'); eventDetails.className = 'event__details'; @@ -64,7 +65,7 @@ export class EventContentPage { const eventImage = document.createElement('img'); eventImage.className = 'event__image'; eventImage.src = endpoint + '/' + event.image; - console.log(eventImage.src); + //console.log(eventImage.src); eventImage.onerror = function () { this.src = "/static/images/placeholder.png"; this.style.objectFit = 'fill'; @@ -109,10 +110,20 @@ export class EventContentPage { const deleteButton = document.createElement('button'); deleteButton.className = 'buttonDelete'; deleteButton.textContent = 'Удалить мероприятие'; + deleteButton.addEventListener("click", async () => { + response = await api.delete(event, event); + console.log(response); + //navigate("/profile", event); + }); const editButton = document.createElement('button'); editButton.className = 'buttonEdit'; editButton.textContent = 'Редактировать мероприятие'; + editButton.addEventListener("click", () => { + console.log("redact ", event); + const currentPath = window.location.pathname; + navigate(currentPath + "/edit"); + }); eventActions.appendChild(editButton); eventActions.appendChild(deleteButton); @@ -122,7 +133,7 @@ export class EventContentPage { } async renderTemplate(id) { - console.log(id); + //console.log(id); const path = `/events/${id}`; const request = { headers: {} }; diff --git a/public/components/Feed/Feed.js b/public/components/Feed/Feed.js index c37db96..9485056 100644 --- a/public/components/Feed/Feed.js +++ b/public/components/Feed/Feed.js @@ -54,6 +54,7 @@ export class Feed { * * @type {Response} */ + console.log(apiPath); const response = await fetch(`${endpoint}${apiPath}`, { /** * The HTTP method for the request. diff --git a/public/components/Search/Search.js b/public/components/Search/Search.js index 3178dbc..f0395ed 100644 --- a/public/components/Search/Search.js +++ b/public/components/Search/Search.js @@ -2,38 +2,40 @@ * Import the endpoint configuration from the config.js file * @import {string} endpoint - The API endpoint URL */ -import { endpoint } from "../../config.js"; +import { endpoint } from "../../config.js" +import { navigate } from "../../modules/router.js"; +import { api } from '../../modules/FrontendAPI.js'; /** * Import the FeedElement component from the FeedElement.js file * @import FeedElement - A component representing a feed element */ -import { FeedElement } from "../FeedElement/FeedElement.js"; +import { FeedElement } from "../FeedElement/FeedElement.js" /** - * Search module. + * Feed module. * - * This module provides a class to render a search feed of events. + * This module provides a class to render a feed of events. * - * @module search + * @module feed */ /** - * Search class. + * Feed class. * - * This class is responsible for rendering a search feed of events. + * This class is responsible for rendering a feed of events. * - * @class Search + * @class Feed */ export class Search { /** - * Renders the search feed of events. + * Renders the feed of events. * * This method fetches the events from the server, creates a FeedElement for each event, and appends them to the feed content. * * @async - * @method renderSearch - * @returns {HTMLElement} The search feed content element. + * @method renderFeed + * @returns {HTMLElement} The feed content element. */ - async renderSearch(navigate, searchQuery) { + async renderSearch(apiPath, searchQuery) { // Create the main container for the search page const searchPage = document.createElement('div'); searchPage.id = 'searchPage'; @@ -95,40 +97,80 @@ export class Search { // Append the searchParameters to the searchPage searchPage.appendChild(searchParameters); - - // Create the feed content element - const feedContent = document.createElement('div'); - feedContent.id = 'feedContent'; - - // Fetch the feed from the server - const fetchFeed = async (tags, searchTerm) => { - const response = await fetch(`${endpoint}/events`, { - method: "GET", + /** + * The feed content element. + * + * @type {HTMLElement} + */ + const feedContent = document.createElement('content'); + + /** + * Fetches the feed from the server. + * + * @async + * @function fetchFeed + */ + const fetchFeed = async () => { + /** + * The response from the server. + * + * @type {Response} + */ + console.log(apiPath); + + try { + const request = { headers: { - + }, - SearchRequest: { - query: searchTerm, - tags: tags, - } + credentials: 'include', + }; + const path = '/events/search?query=' + searchTerm; + const response = await api.get(path, request); + console.log("Search request: ", path); + + if (response.ok) { + /** + * The feed data from the server. + * + * @type {object} + */ + const feed = await response.json(); + feedContent.id = 'feedContent'; + + /** + * Iterates over the feed data and creates a FeedElement for each event. + * + * @param {string} key - The key of the event. + * @param {string} description - The description of the event. + * @param {string} image - The image URL of the event. + */ + Object.entries(feed.events).forEach( (elem) => { + const {id, description, image} = elem[1]; + const feedElement = new FeedElement(id, description, `${endpoint}/${image}`).renderTemplate(); + feedContent.appendChild(feedElement); + feedElement.addEventListener('click', (event) => { + event.preventDefault(); + const path = `/events/${id}`; + navigate(path); }); - - if (response.ok) { - const feed = await response.json(); - Object.entries(feed.events).forEach(([key, { description, id }]) => { - const feedElement = new FeedElement(key, description, id, navigate).renderTemplate(); - feedContent.appendChild(feedElement); - }); - } else { - const errorText = await response.json(); - console.error('Error fetching feed:', errorText); - } - }; - - await fetchFeed(tags, searchTerm); // Calls the fetchFeed function - - // Append the feed content to the main search page - searchPage.appendChild(feedContent); - return searchPage; // Returns the search page element + }); + + } else { + /** + * The error text from the server. + * + * @type {object} + */ + const errorText = await response.json(); + } + } catch (error) { + console.error('Error searching:', error); + } + }; + await fetchFeed(); // Calls the fetchFeed function + searchPage.appendChild(feedContent); + return searchPage; // Returns the search page element } -} + } + \ No newline at end of file diff --git a/public/index.js b/public/index.js index f3ae9b5..c3efdb5 100644 --- a/public/index.js +++ b/public/index.js @@ -174,6 +174,16 @@ const routes = { let eventPage = await new EventContentPage('event').renderTemplate(id); newsFeed.appendChild(eventPage); }, + '/events/:id/edit': async(id) => { + newsFeed.innerHTML = ''; // Clear the modal window content + const categSelect = await loadCategories(); + const formCreate = new EventCreateForm().renderTemplate(categSelect); + console.log(formCreate); + newsFeed.appendChild(formCreate); + const createBtn = document.getElementById('eventSubmitBtn'); + createBtn.addEventListener('click', (event) => handleCreateEventSubmit(event, '/my_events', navigate)); + + }, '/events/my': async(id) => { newsFeed.innerHTML = ''; // Clear the modal window content let UserEventPage = await new UserEventsPage('userEvents').renderTemplate(id); @@ -202,12 +212,19 @@ const routes = { const formCreate = new EventCreateForm().renderTemplate(categSelect); console.log(formCreate); newsFeed.appendChild(formCreate); - const createBtn = document.getElementById('eventSubmitBtn'); - + const createBtn = document.getElementById('eventSubmitBtn'); createBtn.addEventListener('click', (event) => handleCreateEventSubmit(event, '/my_events', navigate)); - //formCreate.addEventListener('submit', (event) => handleCreateEventSubmit(event, '/my_events', navigate)); - - //button to cancel creating + + }, + '/edit_event': async() => { + newsFeed.innerHTML = ''; // Clear the modal window content + const categSelect = await loadCategories(); + const formCreate = new EventCreateForm().renderTemplate(categSelect); + console.log(formCreate); + newsFeed.appendChild(formCreate); + const createBtn = document.getElementById('eventSubmitBtn'); + createBtn.addEventListener('click', (event) => handleCreateEventSubmit(event, '/my_events', navigate)); + }, }; @@ -279,6 +296,12 @@ if (currentPath === '/login' || currentPath === '/signup' || currentPath == '/pr */ const id = currentPath.split('/')[2]; routes['/events/:id'](id); // Вызываем обработчик с id +} else if (/\/events\/\d+(\/edit)?/.test(currentPath)) { + /** + * Call the events route function + */ + const id = currentPath.split('/')[2]; + routes['/events/:id/edit'](id); // Вызываем обработчик с id } else if (/\/events\/categories\/\d+/.test(currentPath)) { /** * Call the events route function From 1e39d98ed23b328703398b3b06751ec955ed7736 Mon Sep 17 00:00:00 2001 From: Ko71k <33371176+Ko71k@users.noreply.github.com> Date: Fri, 8 Nov 2024 13:55:40 +0300 Subject: [PATCH 34/35] Update config.js --- public/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/config.js b/public/config.js index d32c7c7..b4b5f41 100644 --- a/public/config.js +++ b/public/config.js @@ -1 +1 @@ -export const endpoint = "http://127.0.0.1:8080"; +export const endpoint = "http://37.139.40.252/:8080"; From ddaa98a210c3ae96e8532877ba1ac545a4735973 Mon Sep 17 00:00:00 2001 From: Ekaterina Ambartsumova Date: Fri, 8 Nov 2024 21:30:13 +0300 Subject: [PATCH 35/35] fixed config --- public/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/config.js b/public/config.js index b4b5f41..f6c7ec6 100644 --- a/public/config.js +++ b/public/config.js @@ -1 +1 @@ -export const endpoint = "http://37.139.40.252/:8080"; +export const endpoint = "http://37.139.40.252:8080";