diff --git a/index.html b/index.html index 00e3c281d..a857059de 100644 --- a/index.html +++ b/index.html @@ -8,29 +8,6 @@ -
-

- Пожалуйста подождите, комментарии загружаются... -

- -
- - -
- -
-
-
+
diff --git a/js/api/commentsAPI.js b/js/api/commentsAPI.js index 5029054a9..c37e09820 100644 --- a/js/api/commentsAPI.js +++ b/js/api/commentsAPI.js @@ -1,5 +1,5 @@ import { comments } from '../commentsArray/commentsArray.js' -import { renderComments } from '../renderComment/renderComments.js' +import { renderComments } from '../render/renderComments.js' import { formTextArea, formInput, @@ -7,14 +7,24 @@ import { formName, } from '../handlers/formHandlers.js' +const host = 'https://wedev-api.sky.pro/api/v2/MethodGirl' + +// let token = 'asb4c4boc86gasb4c4boc86g37w3cc3bo3b83k4g37k3bk3cg3c03ck4k' + +export let token = '' + +export const updateToken = (newToken) => { + token = newToken +} export function getCommentsAPI() { - return fetch('https://wedev-api.sky.pro/api/v1/MethodGirl/comments', { + return fetch(host + '/comments', { method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + }, }).then((response) => response.json()) } -const host = 'https://wedev-api.sky.pro/api/v1/MethodGirl' - export function postCommentAPI(newComment) { const commentForAPI = { name: newComment.name, @@ -26,10 +36,12 @@ export function postCommentAPI(newComment) { return fetch(host + '/comments', { method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + }, body: JSON.stringify({ text: newComment.text, name: newComment.name, - forceError: true, }), }).then((response) => { if (response.status === 400) { @@ -47,3 +59,55 @@ export function postCommentAPI(newComment) { response.json() }) } + +export function login({ login, password }) { + return fetch('https://wedev-api.sky.pro/api/user/login', { + method: 'POST', + body: JSON.stringify({ login, password }), + }) + .then((response) => { + return response.json().then((data) => { + if (!response.ok) { + console.error('Ответ сервера:', data) + throw new Error( + data.message || + data.error || + `Ошибка ${response.status}`, + ) + } + return data + }) + }) + .catch((error) => { + if ( + error.message.includes('400') || + error.message.includes('Неверный') + ) { + alert('Неверный пароль или логин, попробуйте снова') + } + }) +} + +export function registration({ login, name, password }) { + return fetch('https://wedev-api.sky.pro/api/user', { + method: 'POST', + body: JSON.stringify({ login, name, password }), + }) + .then((response) => { + return response.json().then((data) => { + if (!response.ok) { + console.error('Ответ сервера:', data) + throw new Error( + data.message || + data.error || + `Ошибка ${response.status}`, + ) + } + return data + }) + }) + .catch((error) => { + console.error('Ошибка запроса:', error) + throw error + }) +} diff --git a/js/handlers/addCopyCommentToFormHandlers.js b/js/handlers/addCopyCommentToFormHandlers.js index fb095f901..66bd8a534 100644 --- a/js/handlers/addCopyCommentToFormHandlers.js +++ b/js/handlers/addCopyCommentToFormHandlers.js @@ -1,22 +1,23 @@ -import { humanizeText } from "../textFormater/humanizeText.js"; -import { comments } from "../commentsArray/commentsArray.js"; -import { formComment } from "../handlers/formHandlers.js"; +import { humanizeText } from '../textFormater/humanizeText.js' +import { comments } from '../commentsArray/commentsArray.js' export function addCopyCommentToFormHandlers() { - let commentsEl = document.querySelectorAll(".comment"); + let commentForm = document.querySelector('.add-form-text') - for (let commentEl of commentsEl) { - function copyCommentToForm() { - console.log(commentEl); - let index = commentEl.dataset.id; + let commentsEl = document.querySelectorAll('.comment') - formComment.value = - "Ответ на комментарий: " + - humanizeText(comments[index].text) + - " Автор: " + - humanizeText(comments[index].name); - } + for (let commentEl of commentsEl) { + function copyCommentToForm() { + console.log(commentEl) + let index = commentEl.dataset.id + + commentForm.value = + 'Ответ на комментарий: ' + + humanizeText(comments[index].text) + + ' Автор: ' + + humanizeText(comments[index].name) + } - commentEl.addEventListener("click", copyCommentToForm); - } + commentEl?.addEventListener('click', copyCommentToForm) + } } diff --git a/js/handlers/addLikeHandlers.js b/js/handlers/addLikeHandlers.js index 0d76f70b7..480d74a92 100644 --- a/js/handlers/addLikeHandlers.js +++ b/js/handlers/addLikeHandlers.js @@ -1,24 +1,42 @@ -import { comments } from "../commentsArray/commentsArray.js"; -import { renderComments } from "../renderComment/renderComments.js"; +import { comments } from '../commentsArray/commentsArray.js' +import { renderComments } from '../render/renderComments.js' +import { token } from '../api/commentsAPI.js' export function addLikeHandlers() { - let likeButtons = document.querySelectorAll(".like-button"); - let likesCounter = document.querySelectorAll(".likes-counter"); + let likeButtons = document.querySelectorAll('.like-button') + let likesCounter = document.querySelectorAll('.likes-counter') - likeButtons.forEach((button) => { - button.addEventListener("click", (event) => { - event.stopPropagation(); - let index = button.dataset.index; + likeButtons.forEach((button) => { + button.addEventListener('click', (event) => { + event.stopPropagation() - if (comments[index].isLiked === true) { - comments[index].isLiked = false; - comments[index].likesCounter--; - } else { - comments[index].isLiked = true; - comments[index].likesCounter++; - } + if (token === '') { + alert('Авторизуйтесь, чтобы ставить лайки') + return + } - renderComments(); - }); - }); -} \ No newline at end of file + let index = button.dataset.index + + button.classList.add('shake') + button.addEventListener( + 'animationend', + () => { + button.classList.remove('shake') + }, + { once: true }, + ) + + if (comments[index].isLiked === true) { + button.classList.remove('-active-like') + comments[index].isLiked = false + comments[index].likesCounter-- + } else { + button.classList.add('-active-like') + comments[index].isLiked = true + comments[index].likesCounter++ + } + + likesCounter[index].textContent = comments[index].likesCounter + }) + }) +} diff --git a/js/handlers/formHandlers.js b/js/handlers/formHandlers.js index 5970fea79..80fc08d44 100644 --- a/js/handlers/formHandlers.js +++ b/js/handlers/formHandlers.js @@ -1,5 +1,5 @@ import { comments } from '../commentsArray/commentsArray.js' -import { renderComments } from '../renderComment/renderComments.js' +import { renderComments } from '../render/renderComments.js' import { fixText } from '../textFormater/fixText.js' import { postCommentAPI, getCommentsAPI } from '../api/commentsAPI.js' @@ -10,13 +10,18 @@ export let formInput = document.querySelector('.add-form-name') let formButton = document.querySelector('.add-form-button') -function paintBackground() { - formTextArea.style.backgroundColor = 'white' - formInput.style.backgroundColor = 'white' -} +document.addEventListener('click', (event) => { + let formTextArea = event.target.closest('.add-form-text') + let formInput = event.target.closest('.add-form-name') + + if (formTextArea) { + formTextArea.style.backgroundColor = 'white' + } -formTextArea.addEventListener('click', () => paintBackground(formTextArea)) -formInput.addEventListener('click', () => paintBackground(formInput)) + if (formInput) { + formInput.style.backgroundColor = 'white' + } +}) function delay(ms) { let promise = new Promise((resolve) => { @@ -28,7 +33,23 @@ function delay(ms) { return promise } -formButton.addEventListener('click', async () => { +document.addEventListener('click', async (event) => { + const button = event.target.closest('.add-form-button') + let formTextArea = document.querySelector('.add-form-text') + let formInput = document.querySelector('.add-form-name') + + if (!button) return + + const formComment = document.querySelector('.add-form-text') + const formName = document.querySelector('.add-form-name') + const form = document.querySelector('.add-form') + const commentList = document.querySelector('.comments') + + if (!formComment || !formName || !form) { + console.error('Элементы не найдены') + return + } + try { let commentValue = formComment.value let nameValue = formName.value @@ -92,8 +113,8 @@ formButton.addEventListener('click', async () => { renderComments() formComment.value = '' - formName.value = '' form.style.display = 'flex' + console.log('click') } catch (error) { let form = document.querySelector('.add-form') let loadingMessage = document.querySelector('.comment-creating') diff --git a/js/index.js b/js/index.js index 0f5a8cf53..04398ef86 100644 --- a/js/index.js +++ b/js/index.js @@ -1,6 +1,12 @@ import { getCommentsAPI } from './api/commentsAPI.js' import { comments } from './commentsArray/commentsArray.js' -import { renderComments } from './renderComment/renderComments.js' +import { renderComments } from './render/renderComments.js' +import { renderForm } from './render/renderForm.js' +import { renderLogin } from './render/renderLogin.js' +import { token } from './api/commentsAPI.js' +import { renderLoader } from './render/renderLoader.js' + +renderForm() let loadingMessage = document.querySelector('.loading') @@ -29,7 +35,6 @@ const initPage = async () => { comments.push(...convertedComments) renderComments() - loadingMessage.style.display = 'none' } catch (error) { console.error('Ошибка:', error) @@ -39,3 +44,26 @@ const initPage = async () => { } initPage() + +renderComments() + +export let nameInput = document.querySelector('.add-form-name') +export let commentInput = document.querySelector('.add-form-text') +export let addCommentButton = document.querySelector('.add-form-button') + +if (token === '') { + nameInput.disabled = true + commentInput.disabled = true + addCommentButton.disabled = true +} + +export let autorizationLink = document.querySelector('.autorization-link') +let container = document.querySelector('.container') + +autorizationLink?.addEventListener('click', () => { + renderLogin() + window.scrollTo({ + top: 0, + behavior: 'smooth', + }) +}) diff --git a/js/render/renderAutorizationLink.js b/js/render/renderAutorizationLink.js new file mode 100644 index 000000000..bcc3b7ac2 --- /dev/null +++ b/js/render/renderAutorizationLink.js @@ -0,0 +1,10 @@ +export function renderAutorizationLink() { + let form = document.querySelector('.add-form') + + let autorizationLink = document.createElement('a') + + autorizationLink.className = 'autorization-link' + autorizationLink.textContent = 'Чтобы добавить комментарий, авторизуйтесь' + + form.before(autorizationLink) +} diff --git a/js/render/renderComments.js b/js/render/renderComments.js new file mode 100644 index 000000000..eda14cffe --- /dev/null +++ b/js/render/renderComments.js @@ -0,0 +1,33 @@ +import { comments } from '../commentsArray/commentsArray.js' +import { addLikeHandlers } from '../handlers/addLikeHandlers.js' +import { addCopyCommentToFormHandlers } from '../handlers/addCopyCommentToFormHandlers.js' +import { getFormattedDate } from '../getFormattedDate/getFormattedDate.js' + +export function renderComments() { + let commentsBox = document.querySelector('.comments') + let commentElements = comments + .map((comment, index) => { + return ` +
  • +
    +
    ${comment.name}
    +
    ${getFormattedDate(comment.data)}
    +
    +
    +
    ${comment.text}
    +
    + +
  • ` + }) + .join('') + + commentsBox.innerHTML = commentElements + addCopyCommentToFormHandlers() + addLikeHandlers() + console.log(comments) +} diff --git a/js/render/renderCommentsList.js b/js/render/renderCommentsList.js new file mode 100644 index 000000000..e0d6c22d5 --- /dev/null +++ b/js/render/renderCommentsList.js @@ -0,0 +1,9 @@ +export function renderCommentsList() { + let container = document.querySelector('.container') + + let commentsContainer = document.createElement('ul') + + commentsContainer.className = 'comments' + + container.prepend(commentsContainer) +} diff --git a/js/render/renderForm.js b/js/render/renderForm.js new file mode 100644 index 000000000..e7e17e71c --- /dev/null +++ b/js/render/renderForm.js @@ -0,0 +1,36 @@ +import { renderLoader } from './renderLoader.js' +import { renderCommentsList } from './renderCommentsList.js' +import { renderAutorizationLink } from './renderAutorizationLink.js' + +export function renderForm() { + const container = document.createElement('div') + + let app = document.querySelector('.app') + container.className = 'container' + + container.innerHTML = ` +
    + + +
    + +
    +
    ` + + app.before(container) + + renderAutorizationLink() + renderCommentsList() + renderLoader() +} diff --git a/js/render/renderLoader.js b/js/render/renderLoader.js new file mode 100644 index 000000000..340a85727 --- /dev/null +++ b/js/render/renderLoader.js @@ -0,0 +1,10 @@ +export function renderLoader() { + let container = document.querySelector('.container') + + let loader = document.createElement('p') + + loader.className = 'loading' + loader.textContent = 'Пожалуйста подождите, комментарии загружаются...' + + container.prepend(loader) +} diff --git a/js/render/renderLogin.js b/js/render/renderLogin.js new file mode 100644 index 000000000..01de01dc5 --- /dev/null +++ b/js/render/renderLogin.js @@ -0,0 +1,91 @@ +import { renderRegistration } from './renderRegistration.js' +import { renderForm } from './renderForm.js' +import { renderComments } from './renderComments.js' +import { + login, + registration, + updateToken, + postCommentAPI, +} from '../api/commentsAPI.js' +import { + autorizationLink, + nameInput, + commentInput, + addCommentButton, +} from '../index.js' + +let userName = '' + +export const updateUser = (newUserName) => { + userName = newUserName +} + +function showAuthorizedCommentForm() { + autorizationLink.style.display = 'none' + nameInput.disabled = false + nameInput.readOnly = true + commentInput.disabled = false + addCommentButton.disabled = false +} + +export function renderLogin() { + let app = document.querySelector('.app') + let container = document.querySelector('.container') + + container.style.display = 'none' + + app.innerHTML = ` +
    +

    Логин

    +
    + + +
    +
    + ` + + let registrationButton = document.querySelector('.login__button--register') + + registrationButton?.addEventListener('click', () => renderRegistration()) + + let loginBlock = document.querySelector('.login') + + loginBlock.addEventListener('submit', async (event) => { + event.preventDefault() + + const loginValue = event.target.elements['login'].value + const passwordValue = event.target.elements['password'].value + + if (loginValue < 3 || passwordValue < 3) { + alert('Пароль и логин должны содержать больше 3х символов') + return + } + + try { + const response = await login({ + login: loginValue, + password: passwordValue, + }) + console.log(response) + if (response.user.token) { + updateToken(response.user.token) + updateUser(response.user.name) + + app.style.display = 'none' + container.style.display = 'flex' + nameInput.value = response.user.name + showAuthorizedCommentForm() + } else { + console.error('Не удалось войти') + } + } catch (error) { + console.error('Произошла ошибка', error) + } + }) +} diff --git a/js/render/renderRegistration.js b/js/render/renderRegistration.js new file mode 100644 index 000000000..2d1068f67 --- /dev/null +++ b/js/render/renderRegistration.js @@ -0,0 +1,26 @@ +import { renderLogin } from './renderLogin.js' + +export function renderRegistration() { + let app = document.querySelector('.app') + + app.innerHTML = ` +
    +

    Регистрация

    +
    +
    + + + +
    +
    + + +
    +
    +
    + ` + + let submitButton = document.querySelector('.registration__button--submit') + + submitButton?.addEventListener('click', () => renderLogin()) +} diff --git a/js/renderComment/renderComments.js b/js/renderComment/renderComments.js deleted file mode 100644 index 72212a0ae..000000000 --- a/js/renderComment/renderComments.js +++ /dev/null @@ -1,32 +0,0 @@ -import { comments } from "../commentsArray/commentsArray.js"; -import { addLikeHandlers } from "../handlers/addLikeHandlers.js"; -import { addCopyCommentToFormHandlers } from "../handlers/addCopyCommentToFormHandlers.js"; -import { getFormattedDate } from "../getFormattedDate/getFormattedDate.js"; - -export function renderComments() { - let commentsBox = document.querySelector(".comments"); - let commentElements = comments - .map((comment, index) => { - return `
  • -
    -
    ${comment.name}
    -
    ${getFormattedDate(comment.data)}
    -
    -
    -
    ${comment.text}
    -
    - -
  • `; - }) - .join(""); - - commentsBox.innerHTML = commentElements; - addCopyCommentToFormHandlers(); - addLikeHandlers(); - console.log(comments); -} diff --git a/styles.css b/styles.css index 5af73ba5b..f0086f42e 100644 --- a/styles.css +++ b/styles.css @@ -7,7 +7,9 @@ textarea { transition: background-color 0.3s ease; } -.container { +.container, +.login, +.registration { font-family: Helvetica; color: #ffffff; display: flex; @@ -48,6 +50,204 @@ textarea { border-radius: 20px; } +.login, +.registration { + background: radial-gradient( + 75.42% 75.42% at 50% 42.37%, + rgba(53, 53, 53, 0) 15.92%, + #7334ea 100% + ); +} + +.login__title, +.registration__title { + font-size: 40px; + margin-bottom: 0; +} + +.autorization-link { + cursor: pointer; + border-bottom: 1px solid white; +} + +.registration__input { + margin-top: 24px; + font-size: 20px; + padding: 10px 20px; + background-color: white; + border: none; + border-radius: 18px; + width: 240px; + box-sizing: border-box; +} + +.registration__input:focus-visible { + outline: none; +} + +.registration__input:focus { + border: 1px solid #a180f0; + box-shadow: 0 0 15px rgba(124, 92, 255, 0.35); +} + +.registration__button { + margin-top: 24px; + font-size: 16px; + padding: 10px 20px; + border: none; + border-radius: 18px; + cursor: pointer; +} + +.registration__button--submit { + background-color: transparent; + border: 1px solid #a180f0; +} + +.registration__button--register { + background-color: #a180f0; + position: relative; + overflow: hidden; +} + +@keyframes slideme { + 0% { + left: -200px; + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + left: 200%; + opacity: 0; + } +} + +.registration__button--register::after { + content: ''; + position: absolute; + top: 0; + left: -200px; + width: 60px; + height: 100px; + background-color: white; + filter: blur(30px); + transform: skewX(30deg) translateZ(0); + transition: 1s; +} + +.registration__button--register::before { + content: ''; + position: absolute; + top: 0; + left: -200px; + width: 60px; + height: 100px; + background-color: white; + filter: blur(30px); + transform: skewX(30deg) translateZ(0); + transition: 1s; +} + +.registration__button--register:hover::before { + animation: slideme 1.5s ease; +} + +.registration__input:not(:last-of-type) { + margin-right: 10px; +} + +.registration__input::placeholder { + font-size: 16px; + padding: 0; +} + +.login__input { + margin-top: 24px; + font-size: 20px; + padding: 10px 20px; + background-color: white; + border: none; + border-radius: 18px; + width: 220px; + box-sizing: border-box; +} + +.login__input:first-of-type { + margin-right: 10px; +} + +.login__input:focus-visible { + outline: none; +} + +.login__input:focus { + border: 1px solid #a180f0; + box-shadow: 0 0 15px rgba(124, 92, 255, 0.35); +} + +.login__input::placeholder { + font-size: 16px; + padding: 0; +} + +.login__button { + margin-top: 24px; + font-size: 16px; + padding: 10px 20px; + border: none; + border-radius: 18px; + cursor: pointer; + transition: transform 0.2s; +} + +.login__button:hover { + transform: scale(1.05); +} + +.login__button--submit { + background-color: #a180f0; + margin-right: 10px; + position: relative; + overflow: hidden; +} + +.login__button--register { + background-color: transparent; + border: 1px solid #a180f0; +} + +.login__button--submit::after { + content: ''; + position: absolute; + top: 0; + left: -200px; + width: 60px; + height: 100px; + background-color: white; + filter: blur(30px); + transform: skewX(30deg) translateZ(0); + transition: 1s; +} + +.login__button--submit::before { + content: ''; + position: absolute; + top: 0; + left: -200px; + width: 60px; + height: 100px; + background-color: white; + filter: blur(30px); + transform: skewX(30deg) translateZ(0); + transition: 1s; +} + +.login__button--submit:hover::before { + animation: slideme 1.5s ease; +} + .comments { display: flex; flex-direction: column; @@ -105,13 +305,34 @@ textarea { background-image: url("data:image/svg+xml,%3Csvg width='22' height='20' viewBox='0 0 22 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.95 0C14.036 0 12.199 0.882834 11 2.26703C9.801 0.882834 7.964 0 6.05 0C2.662 0 0 2.6267 0 5.99455C0 10.1035 3.74 13.4714 9.405 18.5613L11 20L12.595 18.5613C18.26 13.4714 22 10.1035 22 5.99455C22 2.6267 19.338 0 15.95 0Z' fill='%23BCEC30'/%3E%3C/svg%3E"); } +.comment:last-of-type { + margin-bottom: 24px; +} + .add-form { + margin-top: 24px; padding: 20px; - margin-top: 48px; display: flex; flex-direction: column; } +@keyframes shake { + 0%, + 100% { + transform: rotate(0deg); + } + 25% { + transform: rotate(-10deg); + } + 75% { + transform: rotate(10deg); + } +} + +.shake { + animation: shake 0.5s ease-in-out; +} + .add-form-name, .add-form-text { font-size: 16px; @@ -120,6 +341,11 @@ textarea { border: none; } +.add-form-name:focus-visible, +.add-form-text:focus-visible { + outline: none; +} + .add-form-name { width: 300px; padding: 11px 22px;