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/README.md b/README.md
index 9a0277a..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)
# Проект
-* 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
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 4955674..2503c6d 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}": [
@@ -32,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/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
diff --git a/public/components/EventContentPage/EventContentPage.css b/public/components/EventContentPage/EventContentPage.css
new file mode 100644
index 0000000..7c8e503
--- /dev/null
+++ b/public/components/EventContentPage/EventContentPage.css
@@ -0,0 +1,147 @@
+/* Основной контейнер для страницы мероприятия */
+.eventPage {
+ display: flex;
+ 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__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 {
+ 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__tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.event__tag {
+ background-color: #009951;
+ color: white;
+ font-size: 20px;
+ padding: 7px;
+ border-radius: 15px;
+}
+
+
+/* Блок для кнопок */
+.event_actionsDiv {
+ margin-top: 15px;
+ display:inline-block;
+ margin-top: 15px;
+}
+
+/* Кнопка удаления */
+.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;
+}
+
+.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;
+}
+
+.event__info-row {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ width: 100%;
+ padding: 10px 0;
+}
+
+.event__tags, .event__date {
+ margin: 0 10px;
+}
+
+.event__actions {
+ display: flex;
+ justify-content: center;
+ margin-top: 30px;
+ gap: 350px;
+}
+
diff --git a/public/components/EventContentPage/EventContentPage.js b/public/components/EventContentPage/EventContentPage.js
new file mode 100644
index 0000000..d504948
--- /dev/null
+++ b/public/components/EventContentPage/EventContentPage.js
@@ -0,0 +1,159 @@
+import { api } from "../../modules/FrontendAPI.js";
+
+import { endpoint } from "../../config.js";
+import { navigate } from "../../modules/router.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);
+
+ 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 = endpoint + '/' + event.image;
+ //console.log(eventImage.src);
+ eventImage.onerror = function () {
+ this.src = "/static/images/placeholder.png";
+ this.style.objectFit = 'fill';
+ };
+
+ 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 = 'Удалить мероприятие';
+ 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);
+
+ this.contentBody.appendChild(eventDetails);
+ this.contentBody.appendChild(eventActions);
+
+ }
+ async renderTemplate(id) {
+
+ //console.log(id);
+ const path = `/events/${id}`;
+ const request = { headers: {} };
+
+ try {
+ const response = await api.get(path, request);
+
+ 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("ERROR HERE");
+ /* some more useful error handling */
+ }
+ return this.contentBody;
+ }
+}
\ No newline at end of file
diff --git a/public/components/EventCreateForm/EventCreateForm.css b/public/components/EventCreateForm/EventCreateForm.css
new file mode 100644
index 0000000..bc351ff
--- /dev/null
+++ b/public/components/EventCreateForm/EventCreateForm.css
@@ -0,0 +1,5 @@
+.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
new file mode 100644
index 0000000..7827ed5
--- /dev/null
+++ b/public/components/EventCreateForm/EventCreateForm.hbs
@@ -0,0 +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}}">{{this.tag}}>
+
+
+ {{else}}
+ <{{this.tag}} class="{{this.className}}" id = "{{this.key}}" data-section="{{this.key}}">{{this.text}}{{this.tag}}>
+ {{/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..cee5ad8
--- /dev/null
+++ b/public/components/EventCreateForm/EventCreateForm.js
@@ -0,0 +1,266 @@
+/**
+ * LoginForm class
+ */
+import { api } from '../../modules/FrontendAPI.js';
+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: 'textarea',
+
+ 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: 'datetime-local',
+
+ className: '',
+ },
+
+ eventBeginError: {
+
+ text: '',
+
+ tag: 'label',
+
+ className: 'error_text',
+
+ type: '',
+ },
+
+ eventEndEntry: {
+ /**
+ * Text
+ * @type {string}
+ */
+ text: 'Время окончания мероприятия',
+ /**
+ * Tag type
+ * @type {string}
+ */
+ tag: 'input',
+ /**
+ * Type
+ * @type {string}
+ */
+ type: 'datetime-local',
+ /**
+ * 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: '',
+ },
+ 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: {
+
+ text: 'Создать',
+
+ tag: 'button',
+
+ type: 'submit',
+
+ className: '',
+ },
+ };
+
+ /**
+ * Renders the form template
+ * @returns {HTMLFormElement} The rendered form
+ */
+ renderTemplate(selectElement) {
+
+ const template = Handlebars.templates['EventCreateForm.hbs'];
+
+ const config = this.config;
+ let itemsArray = Object.entries(config);
+ let items = itemsArray.map(([key, {tag, text, className, type}], index) => {
+ let needPlaceholder = (tag === 'input');
+ 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();
+
+ 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..691667b
--- /dev/null
+++ b/public/components/EventCreateForm/EventCreateForm.precompiled.js
@@ -0,0 +1,70 @@
+(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 " \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)) {
+ return parent[propertyName];
+ }
+ return undefined
+ };
+
+ return " \n \n <"
+ + 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))
+ + "\">"
+ + alias2(alias1((depth0 != null ? lookupProperty(depth0,"tag") : depth0), depth0))
+ + ">\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)) {
+ 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))
+ + "\" data-section=\""
+ + alias2(alias1((depth0 != null ? lookupProperty(depth0,"key") : depth0), depth0))
+ + "\">"
+ + alias2(alias1((depth0 != null ? lookupProperty(depth0,"text") : depth0), depth0))
+ + ""
+ + alias2(alias1((depth0 != null ? lookupProperty(depth0,"tag") : 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":13,"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
diff --git a/public/components/Feed/Feed.js b/public/components/Feed/Feed.js
index ee5dc21..9485056 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
@@ -33,7 +34,7 @@ export class Feed {
* @method renderFeed
* @returns {HTMLElement} The feed content element.
*/
- async renderFeed() {
+ async renderFeed(apiPath) {
/**
* The feed content element.
*
@@ -53,7 +54,8 @@ export class Feed {
*
* @type {Response}
*/
- const response = await fetch(`${endpoint}/events`, {
+ console.log(apiPath);
+ const response = await fetch(`${endpoint}${apiPath}`, {
/**
* The HTTP method for the request.
*
@@ -86,9 +88,15 @@ 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();
+ 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);
+ });
});
} else {
diff --git a/public/components/Header/Header.css b/public/components/Header/Header.css
index 754eacc..8fb3bca 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;
+ padding: 10px 110px 0 20px;
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 {
@@ -44,6 +43,7 @@ button {
padding: 15px 30px;
border-radius: 20px;
font-weight: bold;
+ text-decoration: none;
letter-spacing: 1px;
border: none;
cursor: pointer;
@@ -58,10 +58,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/Header/Header.js b/public/components/Header/Header.js
index f64481a..f9bf976 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.
*
@@ -59,7 +60,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 +106,8 @@ export class Header {
btnRegister.className = "btnRegister";
btnRegister.textContent = "Зарегистрироваться";
buttons.appendChild(btnRegister);
- } else {
+ }
+ if (userIsLoggedIn) {
//User is logged in
/**
* The profile link element.
@@ -106,8 +115,16 @@ export class Header {
* @type {HTMLElement}
*/
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';
@@ -116,6 +133,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 = '/events/my';
+ navigate(path);
+ });
/**
* The logout button element.
@@ -139,11 +164,31 @@ export class Header {
console.error(error);
}
};
+ buttons.appendChild(btnMyEvents);
buttons.appendChild(logoutButton);
}
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/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/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};
});
diff --git a/public/components/Nav/Nav.css b/public/components/Nav/Nav.css
index 013627c..907153c 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 90px;
+ 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/components/Nav/Nav.js b/public/components/Nav/Nav.js
index af0853e..55c23e4 100644
--- a/public/components/Nav/Nav.js
+++ b/public/components/Nav/Nav.js
@@ -1,172 +1,57 @@
-/**
- * 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();
+
+ 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/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..5eefeee
--- /dev/null
+++ b/public/components/Profile/Profile.js
@@ -0,0 +1,159 @@
+import { endpoint } from "../../config.js";
+import { api } from '../../modules/FrontendAPI.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 changeableFields = [
+ { label: 'Username', id: 'username' },
+ { label: 'Email', id: 'email' }
+ ];
+
+ 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) {
+ console.log(profileData);
+ document.getElementById('username').value = profileData.username;
+ document.getElementById('email').value = profileData.email;
+ profilePicture.src = endpoint + '/' + profileData.image || '/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];
+ 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() {
+ 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);
+ }
+ }
+
+ async saveChanges() {
+ const username = document.getElementById('username').value;
+ const email = document.getElementById('email').value;
+ const image = document.getElementById('profileImage').value;
+ try {
+ 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!');
+ } 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/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/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..f0395ed
--- /dev/null
+++ b/public/components/Search/Search.js
@@ -0,0 +1,176 @@
+/**
+ * Import the endpoint configuration from the config.js file
+ * @import {string} endpoint - The API endpoint URL
+ */
+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"
+/**
+ * Feed module.
+ *
+ * This module provides a class to render a feed of events.
+ *
+ * @module feed
+ */
+
+/**
+ * Feed class.
+ *
+ * This class is responsible for rendering a feed of events.
+ *
+ * @class Feed
+ */
+export class Search {
+ /**
+ * 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 renderFeed
+ * @returns {HTMLElement} The feed content element.
+ */
+ async renderSearch(apiPath, 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);
+ /**
+ * 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: {
+
+ },
+ 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);
+ });
+ });
+
+ } 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/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
diff --git a/public/index.css b/public/index.css
index 5a650f0..f3cbb3f 100644
--- a/public/index.css
+++ b/public/index.css
@@ -2,8 +2,14 @@
@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";
+@import "components/Search/Search.css";
+UserEventsPage
body {
- font-family: 'Times New Roman', Times, serif;
+ font-family: Arial, sans-serif;
+ font-weight: bold;
margin: 0;
}
@@ -30,7 +36,7 @@ body {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
- background: #fff;
+ background: #f1f1f1;
padding: 20px;
border: 1px solid #ddd;
border-radius: 10px;
@@ -76,11 +82,7 @@ body {
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
- transition: transform 0.3s ease-in-out;
-}
-
-.feed-element:hover {
- transform: scale(1.05);
+ margin: 20px;
}
.feed-element img {
diff --git a/public/index.html b/public/index.html
index d985863..71bdbec 100644
--- a/public/index.html
+++ b/public/index.html
@@ -5,16 +5,17 @@
Выходной
-
-
+
+
-
-
-
-
+
+
+
+
+