diff --git a/src/.main.rs.swp b/src/.main.rs.swp deleted file mode 100644 index 0773253..0000000 Binary files a/src/.main.rs.swp and /dev/null differ diff --git a/src/main.rs b/src/main.rs index 0543bca..a9d28a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use actix_web::{Error, post, get, web, App, HttpResponse, HttpServer, Responder, HttpRequest}; +use actix_web::{Error, post, get, web, delete, App, HttpResponse, HttpServer, Responder, HttpRequest}; use actix_cors::Cors; use actix_files::Files; use dotenv::dotenv; @@ -366,6 +366,74 @@ async fn add_to_cart( Ok(HttpResponse::Ok().json(json!({"status": "success"}))) } +#[delete("/api/remove-from-cart/{book_id}")] +async fn remove_from_cart( + req: HttpRequest, + pool: web::Data, + book_id: web::Path, +) -> Result { + let user_id = validate_token(get_token(&req)).await?; + let book_id = book_id.into_inner(); + + sqlx::query!( + "DELETE FROM koszyk WHERE user_id = $1 AND book_id = $2", + user_id, + book_id + ) + .execute(pool.get_ref()) + .await + .map_err(|e| actix_web::error::ErrorInternalServerError(e))?; // Dodaj mapowanie błędu + + Ok(HttpResponse::Ok().json(json!({"status": "success"}))) +} + +#[post("/api/decrease-cart-item/{book_id}")] +async fn decrease_cart_item( + req: HttpRequest, + pool: web::Data, + book_id: web::Path, +) -> Result { + let user_id = validate_token(get_token(&req)).await?; + let book_id = book_id.into_inner(); + + // Sprawdź aktualną ilość + let current_quantity = sqlx::query!( + "SELECT quantity FROM koszyk WHERE user_id = $1 AND book_id = $2", + user_id, + book_id + ) + .fetch_optional(pool.get_ref()) + .await + .map_err(|e| actix_web::error::ErrorInternalServerError(e))? // Dodaj mapowanie błędu + .map(|r| r.quantity); + + if let Some(qty) = current_quantity { + if qty > 1 { + // Zmniejsz ilość o 1 + sqlx::query!( + "UPDATE koszyk SET quantity = quantity - 1 WHERE user_id = $1 AND book_id = $2", + user_id, + book_id + ) + .execute(pool.get_ref()) + .await + .map_err(|e| actix_web::error::ErrorInternalServerError(e))?; // Dodaj mapowanie błędu + } else { + // Usuń pozycję jeśli ilość = 1 + sqlx::query!( + "DELETE FROM koszyk WHERE user_id = $1 AND book_id = $2", + user_id, + book_id + ) + .execute(pool.get_ref()) + .await + .map_err(|e| actix_web::error::ErrorInternalServerError(e))?; // Dodaj mapowanie błędu + } + } + + Ok(HttpResponse::Ok().json(json!({"status": "success"}))) +} + #[get("/api/order-history")] async fn get_order_history( req: HttpRequest, @@ -500,7 +568,7 @@ async fn main() -> std::io::Result<()> { HttpServer::new(move || { let cors = Cors::default() .allow_any_origin() - .allowed_methods(vec!["GET", "POST"]) + .allowed_methods(vec!["GET", "POST", "DELETE"]) .allowed_headers(vec![ header::CONTENT_TYPE, header::AUTHORIZATION, @@ -521,6 +589,8 @@ async fn main() -> std::io::Result<()> { .service(add_to_cart) // Dodaj .service(checkout) // Dodaj .service(check_auth) + .service(remove_from_cart) + .service(decrease_cart_item) .service(get_order_history) .service( Files::new("/images", "./static/images") diff --git a/static/css/styles.css b/static/css/styles.css index b4bde74..5a77e78 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -270,6 +270,15 @@ footer a { opacity: 0.7; } +.remove-from-cart { + transition: all 0.3s ease; +} + +.remove-from-cart:hover { + transform: scale(1.05); + box-shadow: 0 0 8px rgba(220, 53, 69, 0.6); +} + .user-links, .anonymous-links { gap: 1rem; align-items: center; @@ -370,3 +379,69 @@ footer a { a { text-decoration: none; } + +/* Koszyk - nowe style */ +.cart-item-details { + display: flex; + flex-direction: column; +} + +.cart-item .card-body { + padding: 1.5rem; +} + +.cart-item-title { + font-size: 1.4rem; + font-weight: bold; + margin-bottom: 0.5rem; +} + +.cart-item-author { + color: #aaa; + font-style: italic; + margin-bottom: 1.5rem; +} + +.cart-item-info { + display: flex; + justify-content: space-between; + gap: 1.5rem; + margin-bottom: 1.5rem; + background-color: #222; + padding: 1.2rem; + border-radius: 8px; + border: 1px solid #333; +} + +.cart-item-info > div { + flex: 1; + min-width: 120px; +} + +.cart-item-price, +.cart-item-quantity, +.cart-item-total { + display: block; + margin-bottom: 0.5rem; + font-size: 0.95rem; + color: #ddd; +} + +.cart-remove-btn, .decrease-quantity { + padding: 0.7rem 1rem; + font-size: 0.95rem; + transition: all 0.3s ease; +} + +.cart-remove-btn:hover, .decrease-quantity:hover { + transform: translateY(-3px); + box-shadow: 0 4px 12px rgba(0,0,0,0.3); +} + +/* Suma całkowita - większy nacisk */ +.total-cart-value { + font-size: 2rem; + font-weight: bold; + color: #ffcc00; + text-shadow: 0 0 8px rgba(255, 204, 0, 0.4); +} diff --git a/static/js/cart.js b/static/js/cart.js index 432eecc..71b5192 100644 --- a/static/js/cart.js +++ b/static/js/cart.js @@ -1,47 +1,3 @@ -async function loadCart() { - try { - const response = await fetch('/api/cart', { - headers: getAuthHeaders() - }); - - if (response.status === 401) { - window.location.href = '/login.html'; - return; - } - - const items = await response.json(); - - if (items.length === 0) { - container.innerHTML = ` -
-

Twój koszyk jest pusty

- Przeglądaj książki -
- `; - return; - } - - const container = document.getElementById('cart-items'); - container.innerHTML = items.map(item => ` -
-
- -
-
${item.tytul}
-

Ilość: ${item.quantity}

-

${item.cena.toString()} PLN

-
-
-
- `).join(''); - } catch (error) { - console.error('Błąd:', error); - showError('Wystąpił błąd podczas ładowania koszyka'); - } -} - document.addEventListener('DOMContentLoaded', async () => { try { const token = localStorage.getItem('token'); @@ -57,24 +13,74 @@ document.addEventListener('DOMContentLoaded', async () => { }); if (!response.ok) throw new Error('Błąd ładowania koszyka'); - + const cartItems = await response.json(); const container = document.getElementById('cart-items'); container.innerHTML = ''; + if (cartItems.length === 0) { + container.innerHTML = '

Twój koszyk jest pusty

'; + return; + } + + let totalCartValue = 0; + cartItems.forEach(item => { + // Formatowanie cen + const price = parseFloat(item.cena); + const formattedPrice = price.toFixed(2); + const itemTotal = price * item.quantity; + const formattedTotal = itemTotal.toFixed(2); + totalCartValue += itemTotal; + const itemHTML = ` -
-
+
+
-
- ${item.tytul} +
+ ${item.tytul}
-
-
-
${item.tytul}
-

Ilość: ${item.quantity}

-

Cena: ${item.cena} PLN

+
+
+
+
${item.tytul}
+

${item.autor}

+
+ +
+
+ Cena jednostkowa + ${formattedPrice} PLN +
+ +
+ Ilość +
+ ${item.quantity} +
+
+ +
+ Suma + ${formattedTotal} PLN +
+
+ +
+
+ + +
+
@@ -84,12 +90,104 @@ document.addEventListener('DOMContentLoaded', async () => { container.insertAdjacentHTML('beforeend', itemHTML); }); + // Dodaj całkowitą sumę koszyka + const totalHTML = ` +
+
+
+

Suma całkowita

+

${totalCartValue.toFixed(2)} PLN

+
+
+
+ `; + container.insertAdjacentHTML('beforeend', totalHTML); + + // Obsługa przycisku zmniejszania ilości + document.querySelectorAll('.decrease-quantity').forEach(button => { + button.addEventListener('click', function() { + const bookId = this.getAttribute('data-book-id'); + updateCartItemQuantity(bookId, 'decrease'); + }); + }); + + // Obsługa przycisku usuwania + document.querySelectorAll('.remove-from-cart').forEach(button => { + button.addEventListener('click', function() { + const bookId = this.getAttribute('data-book-id'); + updateCartItemQuantity(bookId, 'remove'); + }); + }); + } catch (error) { console.error('Error:', error); alert('Nie udało się załadować koszyka'); } }); +async function updateCartItemQuantity(bookId, action) { + try { + const token = localStorage.getItem('token'); + if (!token) { + window.location.href = '/login.html'; + return; + } + + let endpoint, method; + + if (action === 'decrease') { + endpoint = `/api/decrease-cart-item/${bookId}`; + method = 'POST'; + } else if (action === 'remove') { + endpoint = `/api/remove-from-cart/${bookId}`; + method = 'DELETE'; + } else { + throw new Error('Nieznana akcja'); + } + + const response = await fetch(endpoint, { + method: method, + headers: { + 'Authorization': `Bearer ${token}` + } + }); + + if (!response.ok) throw new Error('Błąd aktualizacji koszyka'); + + // Odśwież koszyk + location.reload(); + } catch (error) { + console.error('Error:', error); + alert('Nie udało się zaktualizować koszyka'); + } +} + +async function removeFromCart(bookId) { + try { + const token = localStorage.getItem('token'); + if (!token) { + window.location.href = '/login.html'; + return; + } + + const response = await fetch(`/api/remove-from-cart/${bookId}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${token}` + } + }); + + if (!response.ok) throw new Error('Błąd usuwania z koszyka'); + + // Odśwież koszyk + location.reload(); + } catch (error) { + console.error('Error:', error); + alert('Nie udało się usunąć z koszyka'); + } +} + + document.getElementById('checkoutBtn').addEventListener('click', async () => { try { const token = localStorage.getItem('token'); diff --git a/static/js/main.js b/static/js/main.js index f35403d..a6a5bc3 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -5,7 +5,6 @@ const sortSelect = document.getElementById('sortSelect'); if (!booksContainer || !searchInput || !sortSelect) return; - // Funkcje specyficzne dla strony głównej const createBookCard = (book) => `