вроде почти все
осталось вывод категорий и состояний
This commit is contained in:
100
catalog.html
100
catalog.html
@@ -127,8 +127,20 @@
|
||||
<div class="card h-100 border-0 shadow">
|
||||
<img src="img/stonik.jpg" class="card-img-top" alt="Stone Island">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Stone Island</h5>
|
||||
<h4 class="card-title">Stone Island</h4>
|
||||
<p class="card-text">super idol rovny pacan, groza rayona, mother's modnik, патч на месте</p>
|
||||
|
||||
<!-- Измененная секция Category и Condition -->
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Category:</h5>
|
||||
<p class="card-text">men</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Condition:</h5>
|
||||
<p class="card-text">new</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
@@ -156,6 +168,17 @@
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Adidas</h5>
|
||||
<p class="card-text">sportik, street, baskemtball, air, old school</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Category:</h5>
|
||||
<p class="card-text">men</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Condition:</h5>
|
||||
<p class="card-text">wu</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
@@ -183,6 +206,17 @@
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Napapisaj</h5>
|
||||
<p class="card-text">super idol rovny pacan, groza rayona, mother's modnik, +rep from brothers</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Category:</h5>
|
||||
<p class="card-text">men</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Condition:</h5>
|
||||
<p class="card-text">wu</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
@@ -210,6 +244,17 @@
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Lacoste</h5>
|
||||
<p class="card-text">style, nice, mother's modnik, cotton, krokodil</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Category:</h5>
|
||||
<p class="card-text">uni</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Condition:</h5>
|
||||
<p class="card-text">wu</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
@@ -237,6 +282,17 @@
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Samba</h5>
|
||||
<p class="card-text">super idol rovny pacan, groza rayona, mother's modnik, +rep from brothers</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Category:</h5>
|
||||
<p class="card-text">women</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Condition:</h5>
|
||||
<p class="card-text">new</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
@@ -288,30 +344,8 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
function handleLikeButtonClick() {
|
||||
const icon = this.querySelector("i");
|
||||
this.classList.toggle("liked");
|
||||
|
||||
if (this.classList.contains("liked")) {
|
||||
this.classList.remove("btn-outline-secondary");
|
||||
this.classList.add("btn-outline-danger");
|
||||
icon.classList.remove("bi-heart");
|
||||
icon.classList.add("bi-heart-fill");
|
||||
} else {
|
||||
this.classList.remove("btn-outline-danger");
|
||||
this.classList.add("btn-outline-secondary");
|
||||
icon.classList.remove("bi-heart-fill");
|
||||
icon.classList.add("bi-heart");
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll(".like-btn").forEach((button) => {
|
||||
button.addEventListener("click", handleLikeButtonClick);
|
||||
});
|
||||
|
||||
document.getElementById("addProductForm").addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const name = document.getElementById("productName").value;
|
||||
const price = document.getElementById("productPrice").value;
|
||||
const description = document.getElementById("productDescription").value;
|
||||
@@ -345,17 +379,27 @@
|
||||
</div>
|
||||
`;
|
||||
|
||||
const container = document.getElementById("productsContainer");
|
||||
container.insertAdjacentHTML("beforeend", productCard);
|
||||
|
||||
container.lastElementChild.querySelector(".like-btn").addEventListener("click", handleLikeButtonClick);
|
||||
|
||||
document.getElementById("productsContainer").insertAdjacentHTML("beforeend", productCard);
|
||||
this.reset();
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script type="module">
|
||||
import { LikesModel } from '/components/likes/model.js';
|
||||
import { LikesView } from '/components/likes/view.js';
|
||||
import { LikesController } from '/components/likes/controller.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (document.getElementById('productsContainer')) {
|
||||
const likesModel = new LikesModel();
|
||||
const likesView = new LikesView();
|
||||
new LikesController(likesModel, likesView);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import { Model } from '/components/basket/model.js';
|
||||
import { View } from '/components/basket/view.js';
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// components/likes/controller.js
|
||||
export class LikesController {
|
||||
constructor(model, view) {
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (window.location.pathname.includes('likes.html')) {
|
||||
await this.loadLikes();
|
||||
this.setupLikesEventListeners();
|
||||
}
|
||||
|
||||
if (window.location.pathname.includes('catalog.html')) {
|
||||
this.setupCatalogEventListeners();
|
||||
}
|
||||
}
|
||||
|
||||
async loadLikes() {
|
||||
const items = await this.model.getLikesItems();
|
||||
this.view.showLikes(items);
|
||||
}
|
||||
|
||||
setupLikesEventListeners() {
|
||||
document.addEventListener('click', async (e) => {
|
||||
const likeItem = e.target.closest('.like-item');
|
||||
if (!likeItem) return;
|
||||
|
||||
const productId = likeItem.dataset.id;
|
||||
|
||||
// удалить
|
||||
if (e.target.closest('.remove-like-btn')) {
|
||||
await this.model.removeFromLikes(productId);
|
||||
this.view.showNotification('Товар удален из избранного');
|
||||
await this.loadLikes();
|
||||
}
|
||||
|
||||
// перенести в корзину
|
||||
if (e.target.closest('.move-to-basket-btn')) {
|
||||
const items = await this.model.getLikesItems();
|
||||
const product = items.find(item => item.id === productId);
|
||||
if (product) {
|
||||
await this.model.moveToBasket(product);
|
||||
this.view.showNotification('Товар перенесен в корзину');
|
||||
await this.loadLikes();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupCatalogEventListeners() {
|
||||
document.addEventListener('click', async (e) => {
|
||||
const card = e.target.closest('.card');
|
||||
if (!card) return;
|
||||
|
||||
if (e.target.closest('.like-btn')) {
|
||||
const product = this.extractProductData(card);
|
||||
if (product) {
|
||||
await this.model.addToLikes(product);
|
||||
this.view.showNotification('Товар добавлен в избранное!');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
extractProductData(card) {
|
||||
try {
|
||||
const name = card.querySelector('.card-title').textContent;
|
||||
const priceText = card.querySelector('.text-muted').textContent.replace('$', '');
|
||||
const description = card.querySelector('.card-text').textContent;
|
||||
const image = card.querySelector('img').src;
|
||||
|
||||
const id = btoa(`${name}-${priceText}`).substring(0, 8);
|
||||
|
||||
return {
|
||||
id: id,
|
||||
name: name.trim(),
|
||||
price: parseFloat(priceText),
|
||||
description: description.trim(),
|
||||
image: image
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Ошибка при извлечении данных товара:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// components/likes/model.js
|
||||
export class LikesModel {
|
||||
constructor() {
|
||||
this.apiUrl = 'http://localhost:3000';
|
||||
}
|
||||
|
||||
async request(url, options = {}) {
|
||||
const response = await fetch(`${this.apiUrl}${url}`, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async getLikesItems() {
|
||||
try {
|
||||
return await this.request('/likes');
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении избранного:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async addToLikes(product) {
|
||||
try {
|
||||
const items = await this.getLikesItems();
|
||||
const exists = items.find(item => item.id === product.id);
|
||||
if (!exists) {
|
||||
const response = await fetch(`${this.apiUrl}/likes`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(product)
|
||||
});
|
||||
return await response.json();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при добавлении в избранное:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async removeFromLikes(productId) {
|
||||
try {
|
||||
await fetch(`${this.apiUrl}/likes/${productId}`, { method: 'DELETE' });
|
||||
} catch (error) {
|
||||
console.error('Ошибка при удалении из избранного:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async moveToBasket(product) {
|
||||
try {
|
||||
// 1. Добавляем в корзину
|
||||
const basketRes = await fetch(`${this.apiUrl}/basket`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...product, quantity: 1, addedAt: new Date().toISOString() })
|
||||
});
|
||||
await basketRes.json();
|
||||
|
||||
// 2. Удаляем из избранного
|
||||
await this.removeFromLikes(product.id);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при переносе товара в корзину:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// components/likes/view.js
|
||||
export class LikesView {
|
||||
constructor() {
|
||||
this.likesContainer = document.getElementById('likesContainer');
|
||||
this.emptyLikesElement = document.querySelector('.empty-likes');
|
||||
}
|
||||
|
||||
showLikes(items) {
|
||||
if (!this.likesContainer) return;
|
||||
|
||||
if (!items || items.length === 0) {
|
||||
this.showEmptyLikes();
|
||||
return;
|
||||
}
|
||||
|
||||
// 👉 Если товары есть — убираем блок "пусто"
|
||||
this.hideEmptyLikes();
|
||||
|
||||
const likesHTML = items.map(item => this.createLikesItemHTML(item)).join('');
|
||||
|
||||
// 👉 Добавляем заголовок "Избранное"
|
||||
this.likesContainer.innerHTML = `
|
||||
<h2 class="mb-4 text-center">Избранное</h2>
|
||||
<div class="row g-3">${likesHTML}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
createLikesItemHTML(item) {
|
||||
return `
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100 border-0 shadow like-item" data-id="${item.id}">
|
||||
<img src="${item.image}" class="card-img-top" alt="${item.name}">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">${item.name}</h5>
|
||||
<p class="card-text">${item.description}</p>
|
||||
<span class="fw-bold text-muted">$${item.price}</span>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent d-flex justify-content-between">
|
||||
<button class="btn btn-sm btn-outline-danger remove-like-btn">
|
||||
<i class="bi bi-trash"></i> Удалить
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary move-to-basket-btn">
|
||||
<i class="bi bi-cart-plus"></i> В корзину
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
showEmptyLikes() {
|
||||
if (this.emptyLikesElement) {
|
||||
this.emptyLikesElement.style.display = 'block';
|
||||
}
|
||||
if (this.likesContainer) {
|
||||
this.likesContainer.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
hideEmptyLikes() {
|
||||
if (this.emptyLikesElement) {
|
||||
this.emptyLikesElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
showNotification(message, type = 'success') {
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `alert alert-${type === 'success' ? 'success' : 'danger'} alert-dismissible fade show`;
|
||||
notification.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 1050; min-width: 300px;';
|
||||
notification.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
document.body.appendChild(notification);
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) notification.remove();
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
26
db.json
26
db.json
@@ -54,6 +54,10 @@
|
||||
{
|
||||
"id": "2",
|
||||
"name": "women"
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"name": "uni"
|
||||
}
|
||||
],
|
||||
"condition": [
|
||||
@@ -66,6 +70,24 @@
|
||||
"name": "wu"
|
||||
}
|
||||
],
|
||||
"likes": [],
|
||||
"basket": []
|
||||
"likes": [
|
||||
{
|
||||
"id": "U3RvbmUgSXNs",
|
||||
"name": "Stone Island",
|
||||
"price": 1999.99,
|
||||
"description": "super idol rovny pacan, groza rayona, mother's modnik, патч на месте",
|
||||
"image": "http://192.168.31.212:4000/assets/stonik-D_cwcHTM.jpg"
|
||||
}
|
||||
],
|
||||
"basket": [
|
||||
{
|
||||
"id": "U3RvbmUgSXNs",
|
||||
"name": "Stone Island",
|
||||
"price": 1999.99,
|
||||
"description": "super idol rovny pacan, groza rayona, mother's modnik, патч на месте",
|
||||
"image": "http://192.168.31.212:4000/assets/stonik-D_cwcHTM.jpg",
|
||||
"quantity": 1,
|
||||
"addedAt": "2025-09-26T10:54:28.952Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))e(a);new MutationObserver(a=>{for(const i of a)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&e(o)}).observe(document,{childList:!0,subtree:!0});function s(a){const i={};return a.integrity&&(i.integrity=a.integrity),a.referrerPolicy&&(i.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?i.credentials="include":a.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function e(a){if(a.ep)return;a.ep=!0;const i=s(a);fetch(a.href,i)}})();class r{constructor(){this.apiUrl="http://localhost:3000"}async request(t,s={}){try{const e=await fetch(`${this.apiUrl}${t}`,{...s,headers:{"Content-Type":"application/json",...s.headers}});if(!e.ok)throw new Error(`HTTP error! status: ${e.status}`);return await e.json()}catch(e){throw console.error("Request failed:",e),e}}async getBasketItems(){try{return await this.request("/basket")}catch(t){return console.error("Ошибка при получении корзины:",t),[]}}async addToBasket(t){try{const e=(await this.getBasketItems()).find(a=>a.id===t.id);if(e)await this.updateBasketItem(t.id,e.quantity+1);else{const a={...t,quantity:1,addedAt:new Date().toISOString()};return await(await fetch(`${this.apiUrl}/basket`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})).json()}}catch(s){console.error("Ошибка при добавлении в корзину:",s)}}async updateBasketItem(t,s){try{return await(await fetch(`${this.apiUrl}/basket/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({quantity:s})})).json()}catch(e){console.error("Ошибка при обновлении корзины:",e)}}async removeFromBasket(t){try{await fetch(`${this.apiUrl}/basket/${t}`,{method:"DELETE"})}catch(s){console.error("Ошибка при удалении из корзины:",s)}}async clearBasket(){try{const t=await this.getBasketItems();for(const s of t)await this.removeFromBasket(s.id)}catch(t){console.error("Ошибка при очистке корзины:",t)}}async getProducts(){try{return await(await fetch(`${this.apiUrl}/shmots`)).json()}catch(t){return console.error("Ошибка при получении товаров:",t),[]}}}class c{constructor(){this.basketContainer=document.getElementById("basketContainer"),this.emptyBasketElement=document.querySelector(".empty-basket")}showBasket(t){if(t.length===0){this.showEmptyBasket();return}this.hideEmptyBasket();const s=t.map(e=>this.createBasketItemHTML(e)).join("");this.basketContainer.innerHTML=`
|
||||
class r{constructor(){this.apiUrl="http://localhost:3000"}async request(t,s={}){try{const e=await fetch(`${this.apiUrl}${t}`,{...s,headers:{"Content-Type":"application/json",...s.headers}});if(!e.ok)throw new Error(`HTTP error! status: ${e.status}`);return await e.json()}catch(e){throw console.error("Request failed:",e),e}}async getBasketItems(){try{return await this.request("/basket")}catch(t){return console.error("Ошибка при получении корзины:",t),[]}}async addToBasket(t){try{const e=(await this.getBasketItems()).find(a=>a.id===t.id);if(e)await this.updateBasketItem(t.id,e.quantity+1);else{const a={...t,quantity:1,addedAt:new Date().toISOString()};return await(await fetch(`${this.apiUrl}/basket`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})).json()}}catch(s){console.error("Ошибка при добавлении в корзину:",s)}}async updateBasketItem(t,s){try{return await(await fetch(`${this.apiUrl}/basket/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({quantity:s})})).json()}catch(e){console.error("Ошибка при обновлении корзины:",e)}}async removeFromBasket(t){try{await fetch(`${this.apiUrl}/basket/${t}`,{method:"DELETE"})}catch(s){console.error("Ошибка при удалении из корзины:",s)}}async clearBasket(){try{const t=await this.getBasketItems();for(const s of t)await this.removeFromBasket(s.id)}catch(t){console.error("Ошибка при очистке корзины:",t)}}async getProducts(){try{return await(await fetch(`${this.apiUrl}/shmots`)).json()}catch(t){return console.error("Ошибка при получении товаров:",t),[]}}}class c{constructor(){this.basketContainer=document.getElementById("basketContainer"),this.emptyBasketElement=document.querySelector(".empty-basket")}showBasket(t){if(t.length===0){this.showEmptyBasket();return}this.hideEmptyBasket();const s=t.map(e=>this.createBasketItemHTML(e)).join("");this.basketContainer.innerHTML=`
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow">
|
||||
26
dist/assets/controller-BaU4rR-2.js
vendored
Normal file
26
dist/assets/controller-BaU4rR-2.js
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
class r{constructor(){this.apiUrl="http://localhost:3000"}async request(t,e={}){const s=await fetch(`${this.apiUrl}${t}`,{...e,headers:{"Content-Type":"application/json",...e.headers}});if(!s.ok)throw new Error(`HTTP error! status: ${s.status}`);return await s.json()}async getLikesItems(){try{return await this.request("/likes")}catch(t){return console.error("Ошибка при получении избранного:",t),[]}}async addToLikes(t){try{if(!(await this.getLikesItems()).find(i=>i.id===t.id))return await(await fetch(`${this.apiUrl}/likes`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}catch(e){console.error("Ошибка при добавлении в избранное:",e)}}async removeFromLikes(t){try{await fetch(`${this.apiUrl}/likes/${t}`,{method:"DELETE"})}catch(e){console.error("Ошибка при удалении из избранного:",e)}}async moveToBasket(t){try{await(await fetch(`${this.apiUrl}/basket`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...t,quantity:1,addedAt:new Date().toISOString()})})).json(),await this.removeFromLikes(t.id)}catch(e){console.error("Ошибка при переносе товара в корзину:",e)}}}class c{constructor(){this.likesContainer=document.getElementById("likesContainer"),this.emptyLikesElement=document.querySelector(".empty-likes")}showLikes(t){if(!this.likesContainer)return;if(!t||t.length===0){this.showEmptyLikes();return}this.hideEmptyLikes();const e=t.map(s=>this.createLikesItemHTML(s)).join("");this.likesContainer.innerHTML=`
|
||||
<h2 class="mb-4 text-center">Избранное</h2>
|
||||
<div class="row g-3">${e}</div>
|
||||
`}createLikesItemHTML(t){return`
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100 border-0 shadow like-item" data-id="${t.id}">
|
||||
<img src="${t.image}" class="card-img-top" alt="${t.name}">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">${t.name}</h5>
|
||||
<p class="card-text">${t.description}</p>
|
||||
<span class="fw-bold text-muted">$${t.price}</span>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent d-flex justify-content-between">
|
||||
<button class="btn btn-sm btn-outline-danger remove-like-btn">
|
||||
<i class="bi bi-trash"></i> Удалить
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary move-to-basket-btn">
|
||||
<i class="bi bi-cart-plus"></i> В корзину
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`}showEmptyLikes(){this.emptyLikesElement&&(this.emptyLikesElement.style.display="block"),this.likesContainer&&(this.likesContainer.innerHTML="")}hideEmptyLikes(){this.emptyLikesElement&&(this.emptyLikesElement.style.display="none")}showNotification(t,e="success"){const s=document.createElement("div");s.className=`alert alert-${e==="success"?"success":"danger"} alert-dismissible fade show`,s.style.cssText="position: fixed; top: 20px; right: 20px; z-index: 1050; min-width: 300px;",s.innerHTML=`
|
||||
${t}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`,document.body.appendChild(s),setTimeout(()=>{s.parentNode&&s.remove()},3e3)}}class l{constructor(t,e){this.model=t,this.view=e,this.init()}async init(){window.location.pathname.includes("likes.html")&&(await this.loadLikes(),this.setupLikesEventListeners()),window.location.pathname.includes("catalog.html")&&this.setupCatalogEventListeners()}async loadLikes(){const t=await this.model.getLikesItems();this.view.showLikes(t)}setupLikesEventListeners(){document.addEventListener("click",async t=>{const e=t.target.closest(".like-item");if(!e)return;const s=e.dataset.id;if(t.target.closest(".remove-like-btn")&&(await this.model.removeFromLikes(s),this.view.showNotification("Товар удален из избранного"),await this.loadLikes()),t.target.closest(".move-to-basket-btn")){const o=(await this.model.getLikesItems()).find(n=>n.id===s);o&&(await this.model.moveToBasket(o),this.view.showNotification("Товар перенесен в корзину"),await this.loadLikes())}})}setupCatalogEventListeners(){document.addEventListener("click",async t=>{const e=t.target.closest(".card");if(e&&t.target.closest(".like-btn")){const s=this.extractProductData(e);s&&(await this.model.addToLikes(s),this.view.showNotification("Товар добавлен в избранное!"))}})}extractProductData(t){try{const e=t.querySelector(".card-title").textContent,s=t.querySelector(".text-muted").textContent.replace("$",""),i=t.querySelector(".card-text").textContent,o=t.querySelector("img").src;return{id:btoa(`${e}-${s}`).substring(0,8),name:e.trim(),price:parseFloat(s),description:i.trim(),image:o}}catch(e){return console.error("Ошибка при извлечении данных товара:",e),null}}}export{c as L,l as a,r as b};
|
||||
1
dist/assets/modulepreload-polyfill-B5Qt9EMX.js
vendored
Normal file
1
dist/assets/modulepreload-polyfill-B5Qt9EMX.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))i(e);new MutationObserver(e=>{for(const r of e)if(r.type==="childList")for(const o of r.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&i(o)}).observe(document,{childList:!0,subtree:!0});function s(e){const r={};return e.integrity&&(r.integrity=e.integrity),e.referrerPolicy&&(r.referrerPolicy=e.referrerPolicy),e.crossOrigin==="use-credentials"?r.credentials="include":e.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function i(e){if(e.ep)return;e.ep=!0;const r=s(e);fetch(e.href,r)}})();
|
||||
1
dist/assets/page2-Cyv2CTPS.js
vendored
1
dist/assets/page2-Cyv2CTPS.js
vendored
@@ -1 +0,0 @@
|
||||
import{V as t,C as o,M as d}from"./controller-BWr64z2B.js";/* empty css */document.addEventListener("DOMContentLoaded",function(){if(document.getElementById("basketContainer")||document.getElementById("productsContainer")){const e=new d,n=new t;new o(e,n)}});
|
||||
1
dist/assets/page2-DYag8OKJ.js
vendored
Normal file
1
dist/assets/page2-DYag8OKJ.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import"./modulepreload-polyfill-B5Qt9EMX.js";/* empty css */import{L as t,a as o,b as d}from"./controller-BaU4rR-2.js";import{V as i,C as s,M as r}from"./controller-BHzoImTo.js";document.addEventListener("DOMContentLoaded",function(){if(document.getElementById("productsContainer")){const e=new d,n=new t;new o(e,n)}});document.addEventListener("DOMContentLoaded",function(){if(document.getElementById("basketContainer")||document.getElementById("productsContainer")){const e=new r,n=new i;new s(e,n)}});
|
||||
1
dist/assets/page4-C9wa6TTi.js
vendored
Normal file
1
dist/assets/page4-C9wa6TTi.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import"./modulepreload-polyfill-B5Qt9EMX.js";/* empty css */import{L as o,a as t,b as i}from"./controller-BaU4rR-2.js";document.addEventListener("DOMContentLoaded",function(){if(document.getElementById("likesContainer")){const e=new i,n=new o;new t(e,n)}});
|
||||
1
dist/assets/page5-Cyv2CTPS.js
vendored
1
dist/assets/page5-Cyv2CTPS.js
vendored
@@ -1 +0,0 @@
|
||||
import{V as t,C as o,M as d}from"./controller-BWr64z2B.js";/* empty css */document.addEventListener("DOMContentLoaded",function(){if(document.getElementById("basketContainer")||document.getElementById("productsContainer")){const e=new d,n=new t;new o(e,n)}});
|
||||
1
dist/assets/page5-SW2YIOd4.js
vendored
Normal file
1
dist/assets/page5-SW2YIOd4.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import"./modulepreload-polyfill-B5Qt9EMX.js";/* empty css */import{V as n,C as o,M as d}from"./controller-BHzoImTo.js";document.addEventListener("DOMContentLoaded",function(){if(document.getElementById("basketContainer")||document.getElementById("productsContainer")){const e=new d,t=new n;new o(e,t)}});
|
||||
5
dist/basket.html
vendored
5
dist/basket.html
vendored
@@ -34,8 +34,9 @@
|
||||
main {
|
||||
flex: 1; /* растягивается, чтобы футер был внизу */
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/page5-Cyv2CTPS.js"></script>
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/page5-SW2YIOd4.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-B5Qt9EMX.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/controller-BHzoImTo.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/styles-cpwRBDRQ.css">
|
||||
</head>
|
||||
|
||||
93
dist/catalog.html
vendored
93
dist/catalog.html
vendored
@@ -26,8 +26,10 @@
|
||||
.like-btn i {
|
||||
transition: text-shadow 0.2s ease;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/page2-Cyv2CTPS.js"></script>
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/page2-DYag8OKJ.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-B5Qt9EMX.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/controller-BaU4rR-2.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/controller-BHzoImTo.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/styles-cpwRBDRQ.css">
|
||||
</head>
|
||||
@@ -129,8 +131,20 @@
|
||||
<div class="col">
|
||||
<div class="card h-100 border-0 shadow">
|
||||
<img src="/assets/stonik-D_cwcHTM.jpg" class="card-img-top" alt="Stone Island">
|
||||
<div class="card-body">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Stone Island</h4>
|
||||
<p class="card-text">super idol rovny pacan, groza rayona, mother's modnik, патч на месте</p>
|
||||
|
||||
<!-- Измененная секция Category и Condition -->
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Category:</h5>
|
||||
<p class="card-text">men</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Condition:</h5>
|
||||
<p class="card-text">new</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
@@ -158,6 +172,17 @@
|
||||
<img src="/assets/adidas-gadQV-Lz.jpg" class="card-img-top" alt="Adidas">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Adidas</h5>
|
||||
<p class="card-text">sportik, street, baskemtball, air, old school</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Category:</h5>
|
||||
<p class="card-text">men</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Condition:</h5>
|
||||
<p class="card-text">wu</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
@@ -185,6 +210,17 @@
|
||||
<img src="/assets/napapisaj-DNuXYAGR.jpg" class="card-img-top" alt="Napapisaj">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Napapisaj</h5>
|
||||
<p class="card-text">super idol rovny pacan, groza rayona, mother's modnik, +rep from brothers</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Category:</h5>
|
||||
<p class="card-text">men</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Condition:</h5>
|
||||
<p class="card-text">wu</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
@@ -212,6 +248,17 @@
|
||||
<img src="/assets/lacoste-BSTUvTkt.png" class="card-img-top" alt="Lacoste">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Lacoste</h5>
|
||||
<p class="card-text">style, nice, mother's modnik, cotton, krokodil</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Category:</h5>
|
||||
<p class="card-text">uni</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Condition:</h5>
|
||||
<p class="card-text">wu</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
@@ -239,6 +286,17 @@
|
||||
<img src="/assets/samba-C8wu5LAG.png" class="card-img-top" alt="Samba">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Samba</h5>
|
||||
<p class="card-text">super idol rovny pacan, groza rayona, mother's modnik, +rep from brothers</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Category:</h5>
|
||||
<p class="card-text">women</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5 class="card-text mb-1">Condition:</h5>
|
||||
<p class="card-text">new</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
@@ -290,30 +348,8 @@
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
function handleLikeButtonClick() {
|
||||
const icon = this.querySelector("i");
|
||||
this.classList.toggle("liked");
|
||||
|
||||
if (this.classList.contains("liked")) {
|
||||
this.classList.remove("btn-outline-secondary");
|
||||
this.classList.add("btn-outline-danger");
|
||||
icon.classList.remove("bi-heart");
|
||||
icon.classList.add("bi-heart-fill");
|
||||
} else {
|
||||
this.classList.remove("btn-outline-danger");
|
||||
this.classList.add("btn-outline-secondary");
|
||||
icon.classList.remove("bi-heart-fill");
|
||||
icon.classList.add("bi-heart");
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll(".like-btn").forEach((button) => {
|
||||
button.addEventListener("click", handleLikeButtonClick);
|
||||
});
|
||||
<script>
|
||||
document.getElementById("addProductForm").addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
const name = document.getElementById("productName").value;
|
||||
const price = document.getElementById("productPrice").value;
|
||||
@@ -347,17 +383,14 @@
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const container = document.getElementById("productsContainer");
|
||||
container.insertAdjacentHTML("beforeend", productCard);
|
||||
|
||||
container.lastElementChild.querySelector(".like-btn").addEventListener("click", handleLikeButtonClick);
|
||||
|
||||
document.getElementById("productsContainer").insertAdjacentHTML("beforeend", productCard);
|
||||
this.reset();
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
8
dist/likes.html
vendored
8
dist/likes.html
vendored
@@ -7,6 +7,9 @@
|
||||
<link rel="shortcut icon" href="/assets/favicon-cXvr3Sfo.ico" type="image/x-icon">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||||
|
||||
<script type="module" crossorigin src="/assets/page4-C9wa6TTi.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-B5Qt9EMX.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/controller-BaU4rR-2.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/styles-cpwRBDRQ.css">
|
||||
</head>
|
||||
@@ -59,7 +62,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<main class="container my-4">
|
||||
<main class="container my-4">
|
||||
<div class="text-center py-5 empty-likes">
|
||||
<h1 class="mb-4">Здесь будут лежать товары, которые тебе понравились</h1>
|
||||
<p class="lead mb-4">А пока здесь так пусто...</p>
|
||||
@@ -69,6 +72,8 @@
|
||||
<i class="bi bi-arrow-left me-2"></i>Вернуться в каталог
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="likesContainer"></div>
|
||||
</main>
|
||||
|
||||
@@ -98,5 +103,6 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
|
||||
18
likes.html
18
likes.html
@@ -59,7 +59,7 @@
|
||||
</style>
|
||||
|
||||
<main class="container my-4">
|
||||
<div class="text-center py-5">
|
||||
<div class="text-center py-5 empty-likes">
|
||||
<h1 class="mb-4">Здесь будут лежать товары, которые тебе понравились</h1>
|
||||
<p class="lead mb-4">А пока здесь так пусто...</p>
|
||||
<img src="img/sad2.jpeg" alt="Пусто" class="img-fluid rounded" style="max-height: 300px;">
|
||||
@@ -69,6 +69,8 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="likesContainer"></div>
|
||||
</main>
|
||||
|
||||
<footer class="py-4 mt-5" style="background-color: #00264d; color: white;">
|
||||
@@ -98,5 +100,19 @@
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script type="module">
|
||||
import { LikesModel } from '/components/likes/model.js';
|
||||
import { LikesView } from '/components/likes/view.js';
|
||||
import { LikesController } from '/components/likes/controller.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (document.getElementById('likesContainer')) {
|
||||
const model = new LikesModel();
|
||||
const view = new LikesView();
|
||||
new LikesController(model, view);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user