diff --git a/.gitignore b/.gitignore
index 41fcbdf..a58a280 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,5 @@ public/images
*precompiled.js
+public/closedEye.png
+public/openedEye.png
diff --git a/package-lock.json b/package-lock.json
index 38ac0c2..bea0a06 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.1.0",
"license": "ISC",
"dependencies": {
+ "cors": "^2.8.5",
"express": "^4.21.0",
"handlebars": "^4.7.8",
"morgan": "^1.10.0"
@@ -572,6 +573,19 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"license": "MIT"
},
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -2002,6 +2016,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
diff --git a/package.json b/package.json
index c9f06e0..131013e 100644
--- a/package.json
+++ b/package.json
@@ -6,9 +6,11 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prepare": "husky && husky install",
- "prestart": "npm run compile:templates",
+ "prestart": "npm run compile:templates && npm run compile:forms && npm run compile:header",
"start": "DEBUG=* && node ./server/server.js",
"compile:templates": "handlebars public/components/Cards/cards.hbs -f public/components/Cards/cards.precompiled.js",
+ "compile:forms": "handlebars public/components/Forms/forms.hbs -f public/components/Forms/forms.precompiled.js",
+ "compile:header": "handlebars public/components/Header/header.hbs -f public/components/Header/header.precompiled.js",
"lint": "eslint . --fix && prettier --write ."
},
"author": "",
@@ -28,6 +30,7 @@
]
},
"dependencies": {
+ "cors": "^2.8.5",
"express": "^4.21.0",
"handlebars": "^4.7.8",
"morgan": "^1.10.0"
diff --git a/public/ajax.js b/public/ajax.js
new file mode 100644
index 0000000..9282f2b
--- /dev/null
+++ b/public/ajax.js
@@ -0,0 +1,41 @@
+const HTTP_METHOD_POST = 'POST';
+const HTTP_METHOD_GET = 'GET';
+
+export default async function Ajax({ method, url, body = null }) {
+ const requestBody = {
+ credentials: 'include',
+ method,
+ header: {
+ 'Content-Type': 'application/json',
+ },
+ };
+
+ if (method === HTTP_METHOD_POST) {
+ requestBody.body = JSON.stringify(body);
+ }
+
+ const response = await fetch(url, requestBody);
+
+ let respBody;
+ try {
+ respBody = await response.json();
+ } catch {
+ return {
+ status: response.status,
+ error: 'failed to parse respBody',
+ body: 'failed to parse respBody',
+ };
+ }
+ // console.log('respBody', respBody);
+
+ if (response.status >= 400) {
+ return {
+ status: response.status,
+ error: respBody,
+ };
+ }
+ return {
+ status: response.status,
+ body: respBody,
+ };
+}
diff --git a/public/components/Cards/cards.css b/public/components/Cards/cards.css
index 106e9f7..3ef73dc 100644
--- a/public/components/Cards/cards.css
+++ b/public/components/Cards/cards.css
@@ -5,6 +5,7 @@
display: flex;
padding: var(--space-xs);
width: 100%;
+ box-sizing: border-box;
min-height: var(--card-min-height);
max-height: var(--card-max-height);
gap: var(--space-xs);
@@ -14,6 +15,7 @@
.card-img {
min-width: var(--card-img-min-width);
max-width: var(--card-img-max-width);
+ max-height: 100%;
height: var(--card-img-height);
overflow: hidden;
position: relative;
diff --git a/public/components/Cards/cards.hbs b/public/components/Cards/cards.hbs
index 79de907..eb4190a 100644
--- a/public/components/Cards/cards.hbs
+++ b/public/components/Cards/cards.hbs
@@ -1,13 +1,13 @@
{{#each items}}
- {{#if imageUrl}}
+ {{#if media-url}}
-
+
{{/if}}
-
{{description}}
+
{{body}}
{{/each}}
diff --git a/public/components/Cards/cards.js b/public/components/Cards/cards.js
index 0c7024f..62df95e 100644
--- a/public/components/Cards/cards.js
+++ b/public/components/Cards/cards.js
@@ -1,11 +1,30 @@
+import Ajax from '../../ajax.js';
+
export default class Cards {
constructor(parent, items) {
this.parent = parent;
- this.items = items;
+ this.items = {};
}
- render() {
+ async render() {
const template = Handlebars.templates['cards.hbs'];
+ await this.getCurrentCards2();
this.parent.innerHTML = template({ items: this.items });
}
+
+ async getCurrentCards2() {
+ const response = await Ajax({
+ url: 'http://dead-vc.ru/api/v1/feed',
+ method: 'GET',
+ });
+
+ switch (response.status) {
+ case 200:
+ this.items = response.body.data;
+ console.log(this.items);
+ break;
+ default:
+ console.error('Error', response.status, response.error);
+ }
+ }
}
diff --git a/public/components/Forms/forms.css b/public/components/Forms/forms.css
new file mode 100644
index 0000000..b27d624
--- /dev/null
+++ b/public/components/Forms/forms.css
@@ -0,0 +1,84 @@
+@import '/public/variables.css';
+
+.auth-form {
+ margin-top: 50px;
+ width: 100%;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-s);
+ padding: var(--space-m);
+ background-color: var(--main-color);
+ border-radius: var(--border-radius-m);
+}
+
+.auth-form-links {
+ display: flex;
+ justify-content: center;
+ gap: var(--space-xs);
+}
+
+.active-href {
+ color: var(--hover-color);
+ text-decoration: underline;
+}
+
+.disabled-href {
+ color: var(--hover-color);
+ text-decoration: none;
+}
+
+.auth-form-header {
+ text-align: center;
+ font-size: 24px;
+ font-weight: 600;
+ color: var(--accent-color);
+}
+
+.auth-form-header p {
+ color: var(--accent-color);
+}
+
+.auth-form-inputs {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-xs);
+}
+
+.auth-form-inputs input {
+ padding: 10px;
+ border-radius: 5px;
+ font-size: var(--text-size-xs);
+}
+
+.input-correct {
+ border: 1.5px solid var(--secondary-text-color);
+}
+
+.input-error {
+ border: 1.5px solid var(--error-color);
+}
+
+.label-hint {
+ font-size: var(--text-size-xs);
+ color: var(--text-color);
+}
+
+.label-error {
+ font-size: var(--text-size-xs);
+ color: var(--error-color);
+}
+
+.auth-form-inputs button {
+ padding: 10px;
+ border-radius: 5px;
+ color: var(--main-color);
+ border: 0 none;
+ background-color: var(--accent-color);
+}
+
+.password-rules {
+ line-height: var(--line-height-s);
+ font-size: var(--text-size-xs);
+ color: var(--secondary-text-color);
+}
diff --git a/public/components/Forms/forms.hbs b/public/components/Forms/forms.hbs
new file mode 100644
index 0000000..b8161d8
--- /dev/null
+++ b/public/components/Forms/forms.hbs
@@ -0,0 +1,73 @@
+
\ No newline at end of file
diff --git a/public/components/Forms/forms.js b/public/components/Forms/forms.js
new file mode 100644
index 0000000..82c2612
--- /dev/null
+++ b/public/components/Forms/forms.js
@@ -0,0 +1,147 @@
+import Navigate from '../../navigate.js';
+import Ajax from '../../ajax.js';
+import userState from '../../user.js';
+
+export default class Forms {
+ constructor(parent) {
+ this.parent = parent;
+ this.context = {
+ isReg: false,
+ isEmailCorrect: true,
+ isPasswordCorrect: true,
+ isPasswordRepeatCorrect: true,
+ isApiError: false,
+ apiErrorText: '',
+ };
+ }
+
+ isValidEmail(email) {
+ var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ return emailRegex.test(email);
+ }
+
+ isValidPassword(password) {
+ const passwordRegex = /^[a-zA-Z0-9?!_\-*$]{6,}$/;
+ return passwordRegex.test(password);
+ }
+
+ render() {
+ console.log('new form, isEmailCorrect: ', this.context);
+ const template = Handlebars.templates['forms.hbs'];
+ this.parent.innerHTML = template({ context: this.context });
+ const AuthForm = document.querySelector('.auth-form-inputs');
+
+ AuthForm.addEventListener('submit', this.handleSubmit.bind(this));
+
+ const AuthHref = document.querySelector('#authhref');
+ AuthHref.addEventListener('click', (event) => {
+ event.preventDefault();
+ Navigate('auth');
+ });
+
+ const RegHref = document.querySelector('#reghref');
+ RegHref.addEventListener('click', (event) => {
+ event.preventDefault();
+ Navigate('reg');
+ });
+ }
+
+ handleSubmit(event) {
+ event.preventDefault();
+
+ const EmailInput = document.querySelector('#email-input');
+ const PasswordInput = document.querySelector('#password-input');
+ const EmailInputVal = EmailInput.value.trim();
+ const PasswordInputVal = PasswordInput.value.trim();
+
+ const emailValid = this.isValidEmail(EmailInputVal);
+ const passwordValid = this.isValidPassword(PasswordInputVal);
+
+ this.context.isEmailCorrect = emailValid ? true : false;
+ this.context.isPasswordCorrect = passwordValid ? true : false;
+
+ console.log(EmailInputVal, PasswordInputVal);
+
+ if (this.context.isReg) {
+ const PasswordRepeat = document.querySelector('#password-input-repeat');
+ const PasswordRepeatVal = PasswordRepeat.value.trim();
+ this.context.isPasswordRepeatCorrect = PasswordRepeatVal === PasswordInputVal;
+ }
+
+ if (
+ this.context.isEmailCorrect &&
+ this.context.isPasswordCorrect &&
+ this.context.isPasswordRepeatCorrect
+ ) {
+ if (this.context.isReg) {
+ this.Register({ password: PasswordInputVal, email: EmailInputVal });
+ } else {
+ this.Login({ password: PasswordInputVal, email: EmailInputVal });
+ }
+ } else {
+ this.render();
+ }
+ }
+
+ async Register({ password, email }) {
+ const response = await Ajax({
+ url: 'http://dead-vc.ru/api/v1/register',
+ method: 'POST',
+ body: {
+ password,
+ email,
+ },
+ });
+
+ switch (response.status) {
+ case 200:
+ this.context.isApiError = false;
+ userState.login();
+ userState.setEmail(email);
+ Navigate('feed');
+ break;
+ case 409:
+ console.error('User with this email already exists', response.status, response.error);
+ this.context.isApiError = true;
+ this.context.apiErrorText = 'Пользователь с этим именем существует';
+ this.render();
+ break;
+ default:
+ console.error('Error', response.status, response.error);
+ this.context.isApiError = true;
+ this.context.apiErrorText = 'Ошибка на стороне сервера';
+ this.render();
+ }
+ }
+
+ async Login({ password, email }) {
+ const response = await Ajax({
+ url: 'http://dead-vc.ru/api/v1/login',
+ method: 'POST',
+ body: {
+ password,
+ email,
+ },
+ });
+
+ switch (response.status) {
+ case 200:
+ this.context.isApiError = false;
+ userState.login();
+ userState.setEmail(email);
+ Navigate('feed');
+ break;
+ case 404:
+ console.error('User not found', response.status, response.error);
+ this.context.isApiError = true;
+ this.context.apiErrorText = 'Неверный логин или пароль';
+ this.render();
+ break;
+ default:
+ this.context.isApiError = true;
+ this.context.apiErrorText = 'Ошибка на стороне сервера';
+ console.error('Error', response.status, response.error);
+ this.render();
+ }
+ }
+}
diff --git a/public/components/Header/header.css b/public/components/Header/header.css
new file mode 100644
index 0000000..2bea80b
--- /dev/null
+++ b/public/components/Header/header.css
@@ -0,0 +1,40 @@
+@import '/public/variables.css';
+
+.header {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding: var(--space-xs);
+ height: 50px;
+ background-color: var(--main-color);
+}
+
+.header-content {
+ display: flex;
+ justify-content: space-between;
+ margin-left: 20vw;
+ margin-right: 20vw;
+}
+
+.header-logo {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ font-size: var(--text-size-h2);
+ font-weight: var(--font-weight-bold);
+ color: var(--accent-color);
+}
+
+.header-buttons {
+ gap: var(--space-xs);
+ display: flex;
+}
+
+.header-buttons button {
+ width: 100px;
+ padding: 10px;
+ border-radius: 5px;
+ color: var(--main-color);
+ border: 0 none;
+ background-color: var(--accent-color);
+}
diff --git a/public/components/Header/header.hbs b/public/components/Header/header.hbs
new file mode 100644
index 0000000..2dca469
--- /dev/null
+++ b/public/components/Header/header.hbs
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/public/components/Header/header.js b/public/components/Header/header.js
new file mode 100644
index 0000000..a929cc6
--- /dev/null
+++ b/public/components/Header/header.js
@@ -0,0 +1,46 @@
+import Navigate from '../../navigate.js';
+import userState from '../../user.js';
+import Ajax from '../../ajax.js';
+
+export default class Header {
+ constructor(parent) {
+ this.parent = parent;
+ }
+
+ render() {
+ const template = Handlebars.templates['header.hbs'];
+ this.parent.innerHTML = template({ user: userState });
+
+ if (userState.isAuthorized) {
+ const logoutButton = document.querySelector('#logout-button');
+ logoutButton.addEventListener('click', (event) => {
+ event.preventDefault();
+ this.Logout();
+ });
+ } else {
+ const enterButton = document.querySelector('#enter-button');
+ enterButton.addEventListener('click', (event) => {
+ event.preventDefault();
+ Navigate('auth');
+ });
+ }
+ }
+
+ async Logout() {
+ const response = await Ajax({
+ url: 'http://dead-vc.ru/api/v1/logout',
+ method: 'POST',
+ });
+
+ switch (response.status) {
+ case 200:
+ console.log('Logout successful', response.status, response.body);
+ userState.logout();
+ userState.removeEmail();
+ Navigate('feed');
+ break;
+ default:
+ console.error('Error', response.status, response.error);
+ }
+ }
+}
diff --git a/public/index.css b/public/index.css
index 65dac6c..5f6695a 100644
--- a/public/index.css
+++ b/public/index.css
@@ -1,9 +1,12 @@
@import './components/Cards/cards.css';
+@import './components/Forms/forms.css';
+@import './components/Header/header.css';
@import './variables.css';
body {
margin: 0;
background-color: var(--background-color);
+ font-family: var(--text-font);
}
.main-content {
@@ -12,12 +15,14 @@ body {
justify-content: center;
}
-.main-content__feed {
+.items-container {
margin: var(--space-m);
display: flex;
flex-direction: column;
gap: var(--space-xs);
margin-left: var(--main-content-feed-margin-left);
margin-right: var(--main-content-feed-margin-right);
+ padding: var(--space-xs);
+ width: 100%;
max-width: var(--main-content-feed-max-width);
}
diff --git a/public/index.html b/public/index.html
index 2585e07..14951f4 100644
--- a/public/index.html
+++ b/public/index.html
@@ -14,12 +14,16 @@
/>
+
+
-
+
+
+