This commit is contained in:
Lheorvine 2025-06-01 11:09:37 +02:00
parent 36c36c8afb
commit e19f942547
19 changed files with 1270 additions and 682 deletions

View file

@ -0,0 +1 @@
ALTER TABLE zamowienia ADD COLUMN typ_dostawy VARCHAR(20) NOT NULL DEFAULT 'shipping';

View file

@ -7,6 +7,7 @@ use bigdecimal::BigDecimal;
use serde_json::json; use serde_json::json;
use log; use log;
use std::str::FromStr; // Dodane use std::str::FromStr; // Dodane
use crate::models::CartQuantityUpdate;
#[get("/api/cart")] #[get("/api/cart")]
pub async fn get_cart( pub async fn get_cart(
@ -110,20 +111,22 @@ pub async fn checkout(
let total_bigdecimal = BigDecimal::from_str(&total_str) let total_bigdecimal = BigDecimal::from_str(&total_str)
.map_err(|_| AppError::BadRequest("Invalid total value".to_string()))?; .map_err(|_| AppError::BadRequest("Invalid total value".to_string()))?;
// 1. Utwórz zamówienie // 1. Utwórz zamówienie z typem dostawy
let order_id = sqlx::query!( let order_record = sqlx::query!(
"INSERT INTO zamowienia (user_id, suma_totalna) "INSERT INTO zamowienia (user_id, suma_totalna, typ_dostawy)
VALUES ($1, $2) RETURNING id", VALUES ($1, $2, $3) RETURNING id",
user_id, user_id,
total_bigdecimal total_bigdecimal,
data.delivery_type
) )
.fetch_one(&mut *transaction) .fetch_one(&mut *transaction)
.await .await
.map_err(|e| { .map_err(|e| {
log::error!("Błąd tworzenia zamówienia: {}", e); log::error!("Błąd tworzenia zamówienia: {}", e);
AppError::InternalServerError("Błąd serwera".to_string()) AppError::InternalServerError("Błąd serwera".to_string())
})? })?;
.id;
let order_id = order_record.id;
// 2. Dodaj pozycje zamówienia // 2. Dodaj pozycje zamówienia
for item in &data.items { for item in &data.items {
@ -173,3 +176,28 @@ pub async fn checkout(
Ok(HttpResponse::Ok().json(json!({"status": "success"}))) Ok(HttpResponse::Ok().json(json!({"status": "success"})))
} }
#[post("/api/update-cart-quantity")]
pub async fn update_cart_quantity(
req: HttpRequest,
pool: web::Data<PgPool>,
data: web::Json<CartQuantityUpdate>,
) -> Result<HttpResponse, AppError> {
let user_id = validate_token(&req).await?;
sqlx::query!(
"UPDATE koszyk SET quantity = quantity + $1
WHERE user_id = $2 AND book_id = $3",
data.change,
user_id,
data.book_id
)
.execute(pool.get_ref())
.await
.map_err(|e| {
log::error!("Błąd bazy danych: {}", e);
AppError::InternalServerError("Błąd serwera".to_string())
})?;
Ok(HttpResponse::Ok().json(json!({"status": "success"})))
}

View file

@ -51,6 +51,7 @@ async fn main() -> std::io::Result<()> {
.service(cart::add_to_cart) .service(cart::add_to_cart)
.service(cart::remove_from_cart) .service(cart::remove_from_cart)
.service(cart::checkout) .service(cart::checkout)
.service(cart::update_cart_quantity)
.service(profile::get_order_history) .service(profile::get_order_history)
.service( .service(
Files::new("/images", "./static/images") Files::new("/images", "./static/images")

View file

@ -67,9 +67,15 @@ pub struct OrderWithItems {
pub items: Vec<OrderItem>, pub items: Vec<OrderItem>,
} }
#[derive(Deserialize)]
pub struct CartQuantityUpdate {
pub book_id: i32,
pub change: i32,
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CheckoutRequest { pub struct CheckoutRequest {
pub items: Vec<CartItem>, pub items: Vec<CartItem>,
pub total: f64, pub total: f64,
pub delivery_type: String, // "shipping" lub "local"
} }

View file

@ -12,20 +12,16 @@
<link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet"> <link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
</head> </head>
<body class="dark-theme"> <body>
<nav class="navbar navbar-expand-lg"> <nav class="navbar navbar-expand-lg">
<div class="container"> <div class="container">
<form class="d-flex me-lg-3 flex-grow-1" id="searchForm"> <a class="navbar-brand" href="/">DARK ATHENÆUM</a>
<input class="form-control me-2 dark-input"
type="search"
placeholder="Szukaj książek..."
aria-label="Search"
id="searchInput">
</form>
<a class="navbar-brand mx-lg-auto order-lg-1" href="/">DARK ATHENÆUM</a> <div class="d-flex align-items-center ms-auto">
<button id="theme-toggle" class="btn">
<i class="bi bi-moon-stars-fill"></i>
</button>
<div class="d-flex align-items-center order-lg-2">
<div class="auth-links"> <div class="auth-links">
<div class="anonymous-links"> <div class="anonymous-links">
<a class="navbar-link" href="/login.html">Logowanie</a> <a class="navbar-link" href="/login.html">Logowanie</a>
@ -44,13 +40,13 @@
</nav> </nav>
<main class="container my-5"> <main class="container my-5">
<div class="row" id="book-details"> <div class="row g-4" id="book-details">
<div class="col-md-4"> <div class="col-lg-4 col-md-5">
<div class="cover-container mb-4"> <div class="cover-container mb-4">
<img id="book-cover" class="book-cover" alt="Okładka książki"> <img id="book-cover" class="img-fluid rounded shadow" alt="Okładka książki">
</div> </div>
</div> </div>
<div class="col-md-8"> <div class="col-lg-8 col-md-7">
<h1 id="book-title" class="mb-3 fw-bold"></h1> <h1 id="book-title" class="mb-3 fw-bold"></h1>
<h3 id="book-author" class="mb-4 text-muted"></h3> <h3 id="book-author" class="mb-4 text-muted"></h3>
<p id="book-price" class="fs-2 mb-4 text-primary fw-bold"></p> <p id="book-price" class="fs-2 mb-4 text-primary fw-bold"></p>
@ -91,6 +87,7 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script type="module" src="/js/utils.js"></script> <script type="module" src="/js/utils.js"></script>
<script type="module" src="/js/auth.js"></script> <script type="module" src="/js/auth.js"></script>
<script type="module" src="/js/theme.js"></script>
<script type="module" src="/js/book.js"></script> <script type="module" src="/js/book.js"></script>
</body> </body>
</html> </html>

View file

@ -12,20 +12,16 @@
<link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet"> <link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
</head> </head>
<body class="dark-theme"> <body>
<nav class="navbar navbar-expand-lg"> <nav class="navbar navbar-expand-lg">
<div class="container"> <div class="container">
<form class="d-flex me-lg-3 flex-grow-1" id="searchForm"> <a class="navbar-brand" href="/">DARK ATHENÆUM</a>
<input class="form-control me-2 dark-input"
type="search"
placeholder="Szukaj książek..."
aria-label="Search"
id="searchInput">
</form>
<a class="navbar-brand mx-lg-auto order-lg-1" href="/">DARK ATHENÆUM</a> <div class="d-flex align-items-center ms-auto">
<button id="theme-toggle" class="btn">
<i class="bi bi-moon-stars-fill"></i>
</button>
<div class="d-flex align-items-center order-lg-2">
<div class="auth-links"> <div class="auth-links">
<div class="user-links"> <div class="user-links">
<a class="navbar-link" href="/profile.html">Profil</a> <a class="navbar-link" href="/profile.html">Profil</a>
@ -61,10 +57,20 @@
<span>Wartość produktów:</span> <span>Wartość produktów:</span>
<span class="text-primary" id="products-value">0.00 PLN</span> <span class="text-primary" id="products-value">0.00 PLN</span>
</div> </div>
<div class="d-flex justify-content-between mb-3">
<span>Dostawa:</span> <!-- Zmieniony układ dla opcji dostawy -->
<span>12.99 PLN</span> <div class="mb-3">
<div class="d-flex justify-content-between">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="localPickup">
<label class="form-check-label" for="localPickup">
Dostawa
</label>
</div> </div>
<span id="delivery-value">12.99 PLN</span>
</div>
</div>
<hr> <hr>
<div class="d-flex justify-content-between fw-bold fs-5"> <div class="d-flex justify-content-between fw-bold fs-5">
<span>Do zapłaty:</span> <span>Do zapłaty:</span>
@ -101,6 +107,7 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script type="module" src="/js/utils.js"></script> <script type="module" src="/js/utils.js"></script>
<script type="module" src="/js/auth.js"></script> <script type="module" src="/js/auth.js"></script>
<script type="module" src="/js/theme.js"></script>
<script type="module" src="/js/cart.js"></script> <script type="module" src="/js/cart.js"></script>
</body> </body>
</html> </html>

View file

@ -1,80 +1,272 @@
/* static/css/styles.css */ /* static/css/styles.css */
/* Główne zmienne kolorystyczne */
:root { :root {
--dark-bg: #121212; --dark-primary: #1a2e1a; /* ciemnozielony */
--darker-bg: #0a0a0a; --light-primary: #f8f9fa; /* jasny */
--light-text: #f8f9fa; --gold: #d4af37; /* złoty */
--muted-text: #adb5bd; --dark-text: #f5f5f5;
--primary: #d4af37; /* Złoty - główny kolor */ --light-text: #212529;
--primary-dark: #b38f2d; /* Ciemniejszy złoty */ --dark-secondary: #0d1c0d;
--secondary: #0a1f0a; /* Ciemnozielony - drugi kolor */ --light-secondary: #e9ecef;
--border: #343a40; --dark-card: #142814;
--accent: #228B22; /* Zielony akcent */ --light-card: #ffffff;
} }
.dark-theme { /* Reset i podstawowe style */
background-color: var(--dark-bg); * {
color: var(--light-text); margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: 'Inter', sans-serif;
transition: background-color 0.3s ease, color 0.3s ease;
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-family: 'Inter', sans-serif;
} }
/* Nagłówek - oryginalny styl */ /* Motyw ciemny - domyślny */
body.dark-theme {
background-color: var(--dark-primary);
color: var(--dark-text);
}
body.dark-theme .navbar {
background-color: var(--dark-secondary);
border-bottom: 1px solid rgba(212, 175, 55, 0.2);
}
body.dark-theme .card {
background-color: var(--dark-card);
border: 1px solid rgba(212, 175, 55, 0.2);
color: var(--dark-text);
}
body.dark-theme .form-control,
body.dark-theme .form-select {
background-color: var(--dark-card);
border: 1px solid rgba(212, 175, 55, 0.3);
color: var(--dark-text);
}
body.dark-theme .form-control:focus,
body.dark-theme .form-select:focus {
border-color: var(--gold);
box-shadow: 0 0 0 0.25rem rgba(212, 175, 55, 0.25);
}
body.dark-theme .auth-container {
background-color: var(--dark-card);
border: 1px solid rgba(212, 175, 55, 0.2);
}
body.dark-theme footer {
background-color: var(--dark-secondary);
border-top: 1px solid rgba(212, 175, 55, 0.2);
}
body.dark-theme .order-card {
background-color: #142814;
border: 1px solid rgba(212, 175, 55, 0.2);
color: #f5f5f5;
}
body.dark-theme .order-card .card-header {
background-color: #0d1c0d;
border-bottom: 1px solid rgba(212, 175, 55, 0.2);
color: #f5f5f5;
}
body.dark-theme .order-card .list-group-item {
background-color: #1a2e1a;
border: 1px solid rgba(212, 175, 55, 0.1);
color: #f5f5f5;
}
body.dark-theme .order-card .list-group-item strong {
color: #d4af37;
}
/* Motyw jasny */
body.light-theme {
background-color: var(--light-primary);
color: var(--light-text);
}
body.light-theme .navbar {
background-color: var(--light-secondary);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
body.light-theme .card {
background-color: var(--light-card);
border: 1px solid rgba(0, 0, 0, 0.1);
color: var(--light-text);
}
body.light-theme .form-control,
body.light-theme .form-select {
background-color: var(--light-card);
border: 1px solid rgba(0, 0, 0, 0.1);
color: var(--light-text);
}
body.light-theme .form-control:focus,
body.light-theme .form-select:focus {
border-color: var(--gold);
box-shadow: 0 0 0 0.25rem rgba(212, 175, 55, 0.25);
}
body.light-theme .auth-container {
background-color: var(--light-card);
border: 1px solid rgba(0, 0, 0, 0.1);
}
body.light-theme footer {
background-color: var(--light-secondary);
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
/* Wspólne style dla obu motywów */
.navbar { .navbar {
background-color: #000;
padding: 1rem 0; padding: 1rem 0;
border-bottom: 1px solid var(--border); transition: background-color 0.3s ease;
} }
.navbar-brand { .navbar-brand {
font-family: 'Old English Text MT', serif; font-family: 'Old English Text MT', serif;
font-size: 2.5rem; font-size: 2rem;
color: var(--primary) !important; color: var(--gold) !important;
text-shadow: 0 0 5px rgba(212, 175, 55, 0.5); letter-spacing: 1px;
} }
.navbar-link { .navbar-link {
color: var(--muted-text) !important; color: var(--gold) !important;
text-decoration: none; text-decoration: none;
margin: 0 0.8rem; margin: 0 0.5rem;
transition: color 0.3s; transition: opacity 0.3s ease;
display: flex; font-weight: 500;
align-items: center;
} }
.navbar-link:hover, .navbar-link:focus { .navbar-link:hover {
color: var(--primary) !important; opacity: 0.8;
} }
.navbar-link i { .btn {
margin-right: 0.3rem; background-color: transparent;
border: 2px solid var(--gold);
color: var(--gold) !important;
padding: 0.5rem 1.5rem;
border-radius: 0;
font-weight: 500;
transition: all 0.3s ease;
} }
/* Karty książek - proporcje 5:8 */ .btn:hover {
.book-card { background-color: var(--gold);
height: 100%; color: #fff !important;
display: flex; }
flex-direction: column;
background: var(--darker-bg); .btn-add-to-cart {
border: 1px solid var(--border); padding: 0.75rem 2rem;
font-size: 1.1rem;
}
.btn-gothic {
font-family: 'Old English Text MT', serif;
letter-spacing: 1px;
}
.btn-outline-gothic {
font-family: 'Old English Text MT', serif;
letter-spacing: 1px;
border: 2px solid var(--gold);
color: var(--gold) !important;
}
.btn-outline-gothic:hover {
background-color: var(--gold);
color: #fff !important;
}
.card {
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease; transition: transform 0.3s ease;
} }
.book-card:hover { .card:hover {
transform: translateY(-5px); transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(212, 175, 55, 0.2);
border-color: var(--primary);
} }
.cover-container { .card-img-top {
height: 300px;
object-fit: cover;
width: 100%;
}
.auth-container {
border-radius: 10px;
padding: 2rem;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.text-gothic {
font-family: 'Old English Text MT', serif;
color: var(--gold);
}
footer {
padding: 2rem 0;
margin-top: auto;
}
footer a {
color: var(--gold);
text-decoration: none;
margin: 0 1rem;
}
footer a:hover {
text-decoration: underline;
}
.text-primary {
color: var(--gold) !important;
}
.border-primary {
border-color: var(--gold) !important;
}
/* Styl dla książek */
.books-grid {
display: grid;
grid-template-columns: repeat(5, 1fr); /* 5 kolumn */
gap: 1.5rem;
}
.book-card {
position: relative; position: relative;
padding-bottom: 160%; /* 5:8 proportion (5/8 = 0.625) */
overflow: hidden; overflow: hidden;
background: #000; }
.book-cover-link {
display: block;
text-decoration: none;
}
.book-cover-container {
position: relative;
width: 100%;
padding-top: 160%; /* 5:8 ratio (5/8=0.625 -> 1/0.625=1.6) */
overflow: hidden;
} }
.book-cover { .book-cover {
@ -87,134 +279,184 @@
transition: transform 0.3s ease; transition: transform 0.3s ease;
} }
.cover-container:hover .book-cover { .book-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
padding: 1rem;
text-align: center;
}
.book-overlay h5 {
font-size: 1.25rem;
margin-bottom: 0.5rem;
}
.book-overlay p {
margin-bottom: 0.25rem;
}
.book-overlay .price {
font-weight: bold;
color: #d4af37;
}
.book-cover-link:hover .book-overlay {
opacity: 1;
}
.book-cover-link:hover .book-cover {
transform: scale(1.05); transform: scale(1.05);
} }
.book-body { /* Responsywność */
padding: 1.2rem; @media (max-width: 1200px) {
flex-grow: 1; .books-grid {
display: flex; grid-template-columns: repeat(4, 1fr);
flex-direction: column; }
} }
.book-title { @media (max-width: 992px) {
font-size: 1.1rem; .books-grid {
margin-bottom: 0.5rem; grid-template-columns: repeat(3, 1fr);
color: var(--light-text); }
font-weight: 600;
min-height: 2.8em;
} }
.book-author { @media (max-width: 768px) {
font-size: 0.9rem; .books-grid {
color: var(--muted-text); grid-template-columns: repeat(2, 1fr);
margin-bottom: 0.5rem; }
} }
.book-price { @media (max-width: 576px) {
font-weight: 700; .books-grid {
color: var(--primary); grid-template-columns: 1fr;
margin-top: auto; }
font-size: 1.2rem;
} }
/* Przyciski i elementy interfejsu */ @media (max-width: 768px) {
.btn { .book-cover-container {
background-color: var(--primary); padding-top: 140%; /* Slightly different ratio on mobile */
border: none; }
color: #000; }
padding: 0.8rem 1.5rem;
border-radius: 5px; /* Koszyk */
font-weight: 500; #cart-items .card {
transition: all 0.3s; margin-bottom: 1.5rem;
text-transform: uppercase; }
letter-spacing: 1px;
.card {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.decrease-btn, .increase-btn, .remove-btn {
transition: background-color 0.2s ease, transform 0.2s ease;
}
.decrease-btn:hover, .increase-btn:hover, .remove-btn:hover {
transform: scale(1.1);
}
.quantity-display {
display: inline-block;
min-width: 30px;
text-align: center;
font-weight: bold; font-weight: bold;
} }
.btn:hover { /* Animacje dla ilości */
background-color: var(--primary-dark); .quantity-change {
transform: translateY(-2px); transition: opacity 0.15s ease, transform 0.15s ease;
}
.text-primary {
color: var(--primary) !important;
}
.bg-primary {
background-color: var(--primary) !important;
} }
/* Formularze */ /* Formularze */
.auth-container { .form-control, .form-select {
max-width: 500px; border-radius: 0;
margin: 3rem auto; padding: 0.75rem;
padding: 2.5rem; font-size: 1rem;
background: var(--darker-bg);
border-radius: 10px;
border: 1px solid var(--border);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.auth-container h2 {
text-align: center;
margin-bottom: 1.5rem;
font-weight: 700;
color: var(--primary);
}
.dark-input {
background-color: #1e1e1e;
border: 1px solid var(--border);
color: var(--light-text);
padding: 0.8rem;
border-radius: 5px;
margin-bottom: 1.2rem;
width: 100%;
}
.dark-input:focus {
background-color: #1e1e1e;
color: var(--light-text);
border-color: var(--primary);
box-shadow: 0 0 0 0.25rem rgba(212, 175, 55, 0.25);
}
/* Stopka */
footer {
background-color: #000;
padding: 2.5rem 0;
margin-top: auto;
border-top: 1px solid var(--border);
}
footer a {
color: var(--muted-text);
text-decoration: none;
margin: 0 1rem;
transition: color 0.3s;
}
footer a:hover {
color: var(--primary);
}
footer .text-center {
color: var(--muted-text);
} }
/* Responsywność */ /* Responsywność */
@media (max-width: 768px) { @media (max-width: 768px) {
.navbar-brand { .navbar-brand {
font-size: 1.8rem; font-size: 1.5rem;
} }
.cover-container { .books-grid {
padding-bottom: 130%; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
} }
.book-title { .row.mb-4 {
min-height: auto; flex-direction: column;
gap: 1rem;
}
.col-md-4, .col-md-5, .col-md-3 {
width: 100%;
} }
} }
/* Animacje spinnera */
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner-border {
animation: spin 0.75s linear infinite;
color: var(--gold);
}
/* Alerty */
.alert {
border-radius: 0;
padding: 1rem;
margin-bottom: 1rem;
}
.alert-danger {
background-color: rgba(220, 53, 69, 0.1);
border: 1px solid rgba(220, 53, 69, 0.2);
color: #dc3545;
}
.alert-info {
background-color: rgba(13, 202, 240, 0.1);
border: 1px solid rgba(13, 202, 240, 0.2);
color: #0dcaf0;
}
/* Ikony w koszyku */
.bi {
vertical-align: middle;
}
/* Strona podziękowania */
.py-5 {
padding-top: 6rem !important;
padding-bottom: 6rem !important;
}
.display-3 {
font-family: 'Old English Text MT', serif;
margin-bottom: 2rem;
}
.lead {
font-size: 1.5rem;
margin-bottom: 3rem;
}
/* Układ dla strony profilu */
#order-history .card {
margin-bottom: 1.5rem;
}

View file

@ -15,11 +15,13 @@
<body class="dark-theme"> <body class="dark-theme">
<nav class="navbar navbar-expand-lg"> <nav class="navbar navbar-expand-lg">
<div class="container"> <div class="container">
<div class="d-flex me-lg-3 flex-grow-1"></div> <a class="navbar-brand" href="/">DARK ATHENÆUM</a>
<a class="navbar-brand mx-lg-auto order-lg-1" href="/">DARK ATHENÆUM</a> <div class="d-flex align-items-center ms-auto">
<button id="theme-toggle" class="btn">
<i class="bi bi-moon-stars-fill"></i>
</button>
<div class="d-flex align-items-center order-lg-2">
<div class="auth-links"> <div class="auth-links">
<div class="anonymous-links"> <div class="anonymous-links">
<a class="navbar-link" href="/login.html">Logowanie</a> <a class="navbar-link" href="/login.html">Logowanie</a>
@ -44,7 +46,6 @@
<p class="lead">Odkryj unikalne dzieła literackie w mrocznej atmosferze</p> <p class="lead">Odkryj unikalne dzieła literackie w mrocznej atmosferze</p>
</div> </div>
<!-- Wyszukiwanie przeniesione obok sortowania -->
<div class="col-md-5"> <div class="col-md-5">
<input class="form-control dark-input" <input class="form-control dark-input"
type="search" type="search"
@ -64,7 +65,7 @@
</div> </div>
</div> </div>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-4" id="books-container"> <div class="books-grid" id="books-container">
<div class="col-12 text-center py-5"> <div class="col-12 text-center py-5">
<div class="spinner-border text-primary" role="status"> <div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Ładowanie...</span> <span class="visually-hidden">Ładowanie...</span>
@ -95,6 +96,7 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script type="module" src="/js/utils.js"></script> <script type="module" src="/js/utils.js"></script>
<script type="module" src="/js/auth.js"></script> <script type="module" src="/js/auth.js"></script>
<script type="module" src="/js/theme.js"></script>
<script type="module" src="/js/books.js"></script> <script type="module" src="/js/books.js"></script>
</body> </body>
</html> </html>

View file

@ -1,84 +1,142 @@
import { handleApiError, updateAuthUI, setupLogout } from './utils.js'; import { setTheme } from './theme.js';
document.getElementById('loginForm')?.addEventListener('submit', async (e) => { document.addEventListener('DOMContentLoaded', function() {
const loginForm = document.getElementById('loginForm');
const registerForm = document.getElementById('registerForm');
const logoutLink = document.getElementById('logoutLink');
// Sprawdź status uwierzytelnienia przy załadowaniu
checkAuthStatus();
if (loginForm) {
loginForm.addEventListener('submit', handleLogin);
}
if (registerForm) {
registerForm.addEventListener('submit', handleRegister);
}
if (logoutLink) {
logoutLink.addEventListener('click', handleLogout);
}
});
async function checkAuthStatus() {
try {
const response = await fetch('/api/check-auth', {
credentials: 'include'
});
const data = await response.json();
const authLinks = document.querySelector('.auth-links');
if (authLinks) {
const anonymousLinks = authLinks.querySelector('.anonymous-links');
const userLinks = authLinks.querySelector('.user-links');
if (anonymousLinks && userLinks) {
if (data.authenticated) {
anonymousLinks.style.display = 'none';
userLinks.style.display = 'flex';
} else {
anonymousLinks.style.display = 'flex';
userLinks.style.display = 'none';
}
}
}
} catch (error) {
console.error('Błąd sprawdzania statusu uwierzytelnienia:', error);
}
}
async function handleLogin(e) {
e.preventDefault(); e.preventDefault();
const email = document.getElementById('loginEmail').value;
const password = document.getElementById('loginPassword').value;
try { try {
const response = await fetch('/login', { const response = await fetch('/login', {
method: 'POST', method: 'POST',
credentials: 'include', // dołącz ciasteczka headers: {
headers: { 'Content-Type': 'application/json' }, 'Content-Type': 'application/json'
body: JSON.stringify({ },
email: document.getElementById('loginEmail').value, body: JSON.stringify({ email, haslo: password }),
haslo: document.getElementById('loginPassword').value credentials: 'include'
})
}); });
if (response.ok) { if (response.ok) {
const data = await response.json();
localStorage.setItem('userName', data.imie);
window.location.href = '/'; window.location.href = '/';
} else { } else {
alert('Błąd logowania!'); const errorData = await response.json();
alert(`Błąd logowania: ${errorData.message || 'Nieznany błąd'}`);
} }
} catch (error) { } catch (error) {
console.error('Błąd:', error); console.error('Błąd logowania:', error);
alert('Wystąpił błąd podczas logowania');
} }
}); }
// Rejestracja async function handleRegister(e) {
document.getElementById('registerForm')?.addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
const name = document.getElementById('registerName').value; const name = document.getElementById('registerName').value;
const email = document.getElementById('registerEmail').value; const email = document.getElementById('registerEmail').value;
const password = document.getElementById('registerPassword').value; const password = document.getElementById('registerPassword').value;
const confirmPassword = document.getElementById('registerConfirmPassword').value; const confirmPassword = document.getElementById('registerConfirmPassword').value;
if (password !== confirmPassword) { if (password !== confirmPassword) {
alert('Hasła nie są identyczne'); alert('Hasła nie są identyczne!');
return; return;
} }
try { try {
const response = await fetch('/rejestracja', { const response = await fetch('/rejestracja', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {
body: JSON.stringify({ imie: name, email, haslo: password, confirmPassword }), 'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
haslo: password,
imie: name,
confirm_password: confirmPassword
}),
credentials: 'include'
}); });
if (response.ok) { if (response.ok) {
alert('Rejestracja udana! Możesz się teraz zalogować.'); alert('Konto zostało utworzone! Możesz się zalogować.');
window.location.href = '/login.html'; window.location.href = '/login.html';
} else { } else {
throw new Error('Rejestracja nieudana'); const errorData = await response.json();
alert(`Błąd rejestracji: ${errorData.message || 'Nieznany błąd'}`);
} }
} catch (error) { } catch (error) {
handleApiError(error); console.error('Błąd rejestracji:', error);
} alert('Wystąpił błąd podczas rejestracji');
});
async function checkAuthStatus() {
try {
const response = await fetch('/api/check-auth', {
credentials: 'include' // ważne - dołącz ciasteczka
});
if (!response.ok) return { authenticated: false };
const data = await response.json();
return data;
} catch (error) {
return { authenticated: false };
} }
} }
document.getElementById('logoutLink')?.addEventListener('click', async (e) => { async function handleLogout(e) {
e.preventDefault(); e.preventDefault();
try { try {
await fetch('/api/logout', { const response = await fetch('/logout', {
method: 'POST', method: 'POST',
credentials: 'include' // ważne - dołącz ciasteczka credentials: 'include'
}); });
if (response.ok) {
localStorage.removeItem('userName');
window.location.href = '/'; window.location.href = '/';
} catch (error) { } else {
console.error('Logout error:', error); alert('Wystąpił problem podczas wylogowywania');
} }
}); } catch (error) {
console.error('Błąd wylogowywania:', error);
alert('Wystąpił błąd podczas wylogowywania');
}
}

View file

@ -1,68 +1,77 @@
// static/js/book.js document.addEventListener('DOMContentLoaded', function() {
import { getAuthHeaders, handleApiError } from './utils.js';
document.addEventListener('DOMContentLoaded', async () => {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const bookId = urlParams.get('id'); const bookId = urlParams.get('id');
const bookDetails = document.getElementById('book-details');
if (!bookId) { if (bookId) {
bookDetails.innerHTML = ` loadBookDetails(bookId);
<div class="col-12 text-center py-5">
<h2 class="text-danger">Nieprawidłowe ID książki</h2> // Dodaj obsługę przycisku "Dodaj do koszyka"
<a href="/" class="btn mt-3">Powrót do strony głównej</a> const addToCartBtn = document.querySelector('.btn-add-to-cart');
</div> if (addToCartBtn) {
`; addToCartBtn.addEventListener('click', () => addToCart(bookId));
return;
} }
try {
const response = await fetch(`/api/ksiazki/${bookId}`);
if (!response.ok) throw new Error(`Status: ${response.status}`);
const book = await response.json();
document.getElementById('book-title').textContent = book.tytul;
document.getElementById('book-author').textContent = `Autor: ${book.autor}`;
document.getElementById('book-price').textContent = `Cena: ${book.cena} PLN`;
document.getElementById('book-description').textContent = book.opis;
document.getElementById('book-cover').src = book.obraz_url;
// Dodaj do koszyka
document.querySelector('.btn-add-to-cart').addEventListener('click', async () => {
const token = localStorage.getItem('token');
if (!token) {
alert('Musisz być zalogowany, aby dodać książkę do koszyka');
window.location.href = '/login.html';
return;
}
try {
const response = await fetch('/api/add-to-cart', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({
book_id: parseInt(bookId),
quantity: 1
})
});
if (response.ok) {
alert('Dodano do koszyka!');
} else { } else {
throw new Error('Wystąpił błąd'); document.getElementById('book-details').innerHTML = `
} <div class="alert alert-danger">Nie znaleziono książki</div>
} catch (error) {
handleApiError(error);
}
});
} catch (error) {
bookDetails.innerHTML = `
<div class="col-12 text-center py-5">
<h2 class="text-danger">Błąd ładowania książki</h2>
<p>${error.message}</p>
<a href="/" class="btn mt-3">Powrót do strony głównej</a>
</div>
`; `;
} }
}); });
async function loadBookDetails(bookId) {
try {
const response = await fetch(`/api/ksiazki/${bookId}`, {
credentials: 'include'
});
if (!response.ok) {
throw new Error('Błąd ładowania szczegółów książki');
}
const book = await response.json();
displayBookDetails(book);
} catch (error) {
console.error('Błąd:', error);
document.getElementById('book-details').innerHTML = `
<div class="alert alert-danger">Wystąpił błąd podczas ładowania szczegółów książki</div>
`;
}
}
function displayBookDetails(book) {
document.getElementById('book-title').textContent = book.tytul;
document.getElementById('book-author').textContent = book.autor;
document.getElementById('book-price').textContent = `${book.cena} PLN`;
document.getElementById('book-description').textContent = book.opis;
const bookCover = document.getElementById('book-cover');
if (bookCover) {
bookCover.src = book.obraz_url;
bookCover.alt = `Okładka książki: ${book.tytul}`;
}
}
async function addToCart(bookId) {
try {
const response = await fetch('/api/add-to-cart', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
book_id: parseInt(bookId),
quantity: 1
}),
credentials: 'include'
});
if (response.ok) {
alert('Książka została dodana do koszyka!');
} else {
const errorData = await response.json();
alert(`Błąd: ${errorData.message || 'Nie udało się dodać do koszyka'}`);
}
} catch (error) {
console.error('Błąd:', error);
alert('Wystąpił błąd podczas dodawania do koszyka');
}
}

View file

@ -1,66 +1,137 @@
// static/js/books.js document.addEventListener('DOMContentLoaded', function() {
const booksContainer = document.getElementById('books-container');
import { getAuthHeaders, handleApiError } from './utils.js';
export async function loadBooks(searchTerm = '', sortBy = 'default') {
try {
const response = await fetch(`/api/ksiazki?search=${encodeURIComponent(searchTerm)}&sort=${sortBy}`);
if (!response.ok) throw new Error(`Błąd HTTP: ${response.status}`);
return await response.json();
} catch (error) {
handleApiError(error, 'Wystąpił błąd podczas ładowania książek');
return [];
}
}
export function renderBooks(books, containerId) {
const container = document.getElementById(containerId);
if (!container) return;
if (books.length === 0) {
container.innerHTML = '<div class="col-12 text-center py-5"><h3>Nie znaleziono książek</h3></div>';
return;
}
container.innerHTML = books.map(book => `
<div class="col">
<div class="book-card h-100">
<a href="/book.html?id=${book.id}">
<div class="cover-container">
<img src="${book.obraz_url}" class="book-cover" alt="${book.tytul}">
</div>
</a>
<div class="book-body p-3">
<h5 class="book-title">${book.tytul}</h5>
<div class="mt-auto">
<p class="book-author mb-1">${book.autor}</p>
<p class="book-price mb-0">${book.cena} PLN</p>
</div>
</div>
</div>
</div>
`).join('');
}
// Inicjalizacja na stronie głównej
document.addEventListener('DOMContentLoaded', async () => {
if (!document.getElementById('books-container')) return;
const books = await loadBooks();
renderBooks(books, 'books-container');
// Obsługa wyszukiwania i sortowania
const searchInput = document.getElementById('searchInput'); const searchInput = document.getElementById('searchInput');
const sortSelect = document.getElementById('sortSelect'); const sortSelect = document.getElementById('sortSelect');
const reloadBooks = async () => { // Załaduj książki przy starcie
const books = await loadBooks( loadBooks();
searchInput?.value || '',
sortSelect?.value || 'default'
);
renderBooks(books, 'books-container');
};
searchInput?.addEventListener('input', reloadBooks); // Dodaj event listeners
sortSelect?.addEventListener('change', reloadBooks); if (searchInput) {
searchInput.addEventListener('input', debounce(loadBooks, 300));
}
if (sortSelect) {
sortSelect.addEventListener('change', loadBooks);
}
}); });
async function loadBooks() {
const booksContainer = document.getElementById('books-container');
const searchInput = document.getElementById('searchInput');
const sortSelect = document.getElementById('sortSelect');
if (!booksContainer) return;
booksContainer.innerHTML = `
<div class="col-12 text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Ładowanie...</span>
</div>
</div>
`;
try {
const searchTerm = searchInput ? searchInput.value : '';
const sortBy = sortSelect ? sortSelect.value : 'default';
const response = await fetch(`/api/ksiazki?search=${encodeURIComponent(searchTerm)}&sort=${sortBy}`, {
credentials: 'include'
});
if (!response.ok) {
throw new Error('Błąd ładowania książek');
}
const books = await response.json();
displayBooks(books);
} catch (error) {
console.error('Błąd:', error);
booksContainer.innerHTML = `
<div class="col-12 text-center py-5">
<div class="alert alert-danger">Wystąpił błąd podczas ładowania książek</div>
</div>
`;
}
}
function displayBooks(books) {
const booksContainer = document.getElementById('books-container');
if (!booksContainer) return;
if (books.length === 0) {
booksContainer.innerHTML = `
<div class="col-12 text-center py-5">
<p>Brak książek spełniających kryteria wyszukiwania</p>
</div>
`;
return;
}
booksContainer.innerHTML = '';
books.forEach(book => {
const bookCard = `
<div class="book-card">
<a href="/book.html?id=${book.id}" class="book-cover-link">
<div class="book-cover-container">
<img src="${book.obraz_url}" class="book-cover" alt="${book.tytul}">
<div class="book-overlay">
<h5>${book.tytul}</h5>
<p>${book.autor}</p>
<p class="price">${book.cena} PLN</p>
</div>
</div>
</a>
<button class="btn btn-sm btn-outline-primary w-100 mt-2 add-to-cart-btn" data-book-id="${book.id}">
<i class="bi bi-cart-plus me-1"></i> Dodaj do koszyka
</button>
</div>
`;
booksContainer.innerHTML += bookCard;
});
// Dodajemy nasłuchiwanie na przyciskach dodania do koszyka
document.querySelectorAll('.add-to-cart-btn').forEach(button => {
button.addEventListener('click', function() {
const bookId = this.dataset.bookId;
addToCart(bookId);
});
});
}
// Funkcja dodająca do koszyka
async function addToCart(bookId) {
try {
const response = await fetch('/api/add-to-cart', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
book_id: parseInt(bookId),
quantity: 1
}),
credentials: 'include'
});
if (response.ok) {
alert('Książka została dodana do koszyka!');
} else {
const errorData = await response.json();
alert(`Błąd: ${errorData.message || 'Nie udało się dodać do koszyka'}`);
}
} catch (error) {
console.error('Błąd:', error);
alert('Wystąpił błąd podczas dodawania do koszyka');
}
}
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}

View file

@ -1,194 +1,298 @@
// static/js/cart.js document.addEventListener('DOMContentLoaded', function() {
const cartItemsContainer = document.getElementById('cart-items');
const checkoutBtn = document.getElementById('checkoutBtn');
const localPickupCheckbox = document.getElementById('localPickup');
import { getAuthHeaders, handleApiError } from './utils.js'; // Załaduj zawartość koszyka
loadCart();
document.addEventListener('DOMContentLoaded', async () => { // Dodaj event listeners
try { if (localPickupCheckbox) {
const token = localStorage.getItem('token'); // JEDEN listener dla checkboxa
if (!token) { localPickupCheckbox.addEventListener('change', function() {
window.location.href = '/login.html'; // Pobierz aktualny stan koszyka i zaktualizuj podsumowanie
return; const cartItems = Array.from(document.querySelectorAll('.card.mb-3')).map(card => {
} const bookId = card.querySelector('.decrease-btn').dataset.bookId;
const quantity = parseInt(card.querySelector('.quantity-display').textContent);
const cena = parseFloat(card.querySelector('.card-text').textContent.replace(' PLN', ''));
const tytul = card.querySelector('.card-title').textContent;
const obraz_url = card.querySelector('img').src;
const response = await fetch('/api/cart', { return {
headers: getAuthHeaders() book_id: parseInt(bookId),
quantity,
cena,
tytul,
obraz_url
};
}); });
if (!response.ok) throw new Error('Błąd ładowania koszyka'); updateSummary(cartItems);
const cartItems = await response.json(); });
renderCart(cartItems); }
} catch (error) { if (checkoutBtn) {
handleApiError(error, 'Nie udało się załadować koszyka'); checkoutBtn.addEventListener('click', handleCheckout);
} }
}); });
function renderCart(cartItems) { async function loadCart() {
const container = document.getElementById('cart-items'); const cartItemsContainer = document.getElementById('cart-items');
container.innerHTML = '';
if (cartItems.length === 0) { if (!cartItemsContainer) return;
container.innerHTML = '<p class="text-center">Twój koszyk jest pusty</p>';
return; cartItemsContainer.innerHTML = `
<div class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Ładowanie...</span>
</div>
</div>
`;
try {
const response = await fetch('/api/cart', {
credentials: 'include'
});
if (!response.ok) {
throw new Error(`Błąd HTTP: ${response.status}`);
} }
let totalCartValue = 0; const cartItems = await response.json();
displayCartItems(cartItems);
cartItems.forEach(item => { updateSummary(cartItems);
const price = parseFloat(item.cena); } catch (error) {
const itemTotal = price * item.quantity; console.error('Błąd ładowania koszyka:', error);
totalCartValue += itemTotal; cartItemsContainer.innerHTML = `
<div class="alert alert-danger">Wystąpił błąd podczas ładowania koszyka: ${error.message}</div>
const itemHTML = `
<div class="col-12 mb-4 cart-item">
<div class="card dark-card">
<div class="row g-0">
<div class="col-md-4 text-center p-3 cart-item-image">
<img src="${item.obraz_url}"
class="img-fluid rounded"
alt="${item.tytul}"
style="max-height: 200px;">
</div>
<div class="col-md-8 cart-item-details">
<div class="card-body h-100 d-flex flex-column">
<div>
<h5 class="card-title cart-item-title">${item.tytul}</h5>
<p class="card-text cart-item-author">${item.autor}</p>
</div>
<div class="d-flex flex-wrap gap-3 mb-2 cart-item-info mt-auto">
<div class="d-flex flex-column flex-grow-1">
<span class="fw-bold cart-item-price">Cena jednostkowa</span>
<span class="fs-5">${price.toFixed(2)} PLN</span>
</div>
<div class="d-flex flex-column">
<span class="fw-bold">Ilość</span>
<div class="d-flex align-items-center gap-2">
<span class="fs-5 badge bg-dark rounded-pill">${item.quantity}</span>
</div>
</div>
<div class="d-flex flex-column">
<span class="fw-bold cart-item-total">Suma</span>
<span class="fs-5 text-warning">${itemTotal.toFixed(2)} PLN</span>
</div>
</div>
<div class="mt-3">
<div class="btn-group w-100" role="group">
<button class="btn btn-sm btn-outline-secondary decrease-quantity flex-grow-1"
data-book-id="${item.book_id}">
<i class="bi bi-dash"></i> Zmniejsz ilość
</button>
<button class="btn btn-sm btn-outline-danger remove-from-cart cart-remove-btn flex-grow-1"
data-book-id="${item.book_id}">
<i class="bi bi-trash"></i> Usuń
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`; `;
container.insertAdjacentHTML('beforeend', itemHTML); }
});
// Suma całkowita
const totalHTML = `
<div class="col-12 mt-4">
<div class="card dark-card p-4">
<div class="d-flex justify-content-between align-items-center">
<h2 class="mb-0">Suma całkowita</h2>
<h2 class="mb-0 total-cart-value">${totalCartValue.toFixed(2)} PLN</h2>
</div>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', totalHTML);
// Obsługa przycisków
document.querySelectorAll('.decrease-quantity').forEach(button => {
button.addEventListener('click', function() {
const bookId = this.dataset.bookId;
updateCartItemQuantity(bookId, 'decrease');
});
});
document.querySelectorAll('.remove-from-cart').forEach(button => {
button.addEventListener('click', function() {
const bookId = this.dataset.bookId;
updateCartItemQuantity(bookId, 'remove');
});
});
// Finalizacja zamówienia
document.getElementById('checkoutBtn')?.addEventListener('click', handleCheckout);
} }
async function updateCartItemQuantity(bookId, action) { function displayCartItems(cartItems) {
try { const cartItemsContainer = document.getElementById('cart-items');
const token = localStorage.getItem('token');
if (!token) { if (!cartItemsContainer) return;
window.location.href = '/login.html';
if (cartItems.length === 0) {
cartItemsContainer.innerHTML = `
<div class="alert alert-info">
Twój koszyk jest pusty
</div>
`;
return; return;
} }
let method, endpoint; cartItemsContainer.innerHTML = '';
if (action === 'decrease') {
endpoint = `/api/decrease-cart-item/${bookId}`;
method = 'POST';
} else {
endpoint = `/api/remove-from-cart/${bookId}`;
method = 'DELETE';
}
const response = await fetch(endpoint, { cartItems.forEach(item => {
method, const itemElement = `
headers: getAuthHeaders() <div class="card mb-3">
<div class="row g-0">
<div class="col-md-2">
<img src="${item.obraz_url}" class="img-fluid rounded-start" alt="${item.tytul}">
</div>
<div class="col-md-6">
<div class="card-body">
<h5 class="card-title">${item.tytul}</h5>
<p class="card-text">${item.cena} PLN</p>
</div>
</div>
<div class="col-md-4 d-flex align-items-center justify-content-end pe-3">
<div class="d-flex align-items-center">
<button class="btn btn-sm btn-outline-secondary decrease-btn" data-book-id="${item.book_id}">
<i class="bi bi-dash"></i>
</button>
<span class="mx-2 quantity-display">${item.quantity}</span>
<button class="btn btn-sm btn-outline-secondary increase-btn" data-book-id="${item.book_id}">
<i class="bi bi-plus"></i>
</button>
<button class="btn btn-sm btn-danger ms-2 remove-btn" data-book-id="${item.book_id}">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
</div>
`;
cartItemsContainer.innerHTML += itemElement;
}); });
if (!response.ok) throw new Error('Błąd aktualizacji koszyka'); // Dodaj event listeners dla przycisków
location.reload(); document.querySelectorAll('.decrease-btn').forEach(btn => {
btn.addEventListener('click', () => updateCartItemQuantity(btn.dataset.bookId, -1));
});
document.querySelectorAll('.increase-btn').forEach(btn => {
btn.addEventListener('click', () => updateCartItemQuantity(btn.dataset.bookId, 1));
});
document.querySelectorAll('.remove-btn').forEach(btn => {
btn.addEventListener('click', () => removeCartItem(btn.dataset.bookId));
});
}
async function updateCartItemQuantity(bookId, change) {
try {
const response = await fetch('/api/update-cart-quantity', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
book_id: parseInt(bookId),
change: change
}),
credentials: 'include'
});
if (response.ok) {
const quantityElement = document.querySelector(`.decrease-btn[data-book-id="${bookId}"]`).nextElementSibling;
const currentQuantity = parseInt(quantityElement.textContent);
const newQuantity = currentQuantity + change;
// Jeśli nowa ilość jest mniejsza lub równa 0, usuń produkt
if (newQuantity <= 0) {
const cartItem = quantityElement.closest('.card');
cartItem.style.opacity = '0';
setTimeout(() => {
cartItem.remove();
updateSummaryFromDOM();
}, 300);
// Dodatkowo wyślij żądanie usunięcia z serwera
await fetch(`/api/remove-from-cart/${bookId}`, {
method: 'DELETE',
credentials: 'include'
});
} else {
// Animacja zmiany ilości
quantityElement.textContent = newQuantity;
updateSummaryFromDOM();
}
} else {
alert('Błąd aktualizacji ilości');
}
} catch (error) { } catch (error) {
handleApiError(error); console.error('Błąd:', error);
alert('Wystąpił błąd podczas aktualizacji ilości');
}
}
// Funkcja do aktualizacji podsumowania na podstawie DOM
function updateSummaryFromDOM() {
const cartItems = Array.from(document.querySelectorAll('.card.mb-3')).map(card => {
const bookId = card.querySelector('.decrease-btn').dataset.bookId;
const quantity = parseInt(card.querySelector('.quantity-display').textContent);
const cena = parseFloat(card.querySelector('.card-text').textContent.replace(' PLN', ''));
const tytul = card.querySelector('.card-title').textContent;
const obraz_url = card.querySelector('img').src;
return {
book_id: parseInt(bookId),
quantity,
cena,
tytul,
obraz_url
};
});
updateSummary(cartItems);
}
function updateSummary(cartItems) {
const localPickupCheckbox = document.getElementById('localPickup');
const productsValueEl = document.getElementById('products-value');
const deliveryValueEl = document.getElementById('delivery-value');
const totalValueEl = document.getElementById('total-value');
if (!productsValueEl || !deliveryValueEl || !totalValueEl) return;
// Oblicz wartość produktów
let productsValue = 0;
if (cartItems && cartItems.length > 0) {
productsValue = cartItems.reduce((total, item) => {
return total + (item.cena * item.quantity);
}, 0);
}
productsValueEl.textContent = productsValue.toFixed(2) + ' PLN';
// Oblicz koszt dostawy
const deliveryCost = localPickupCheckbox && localPickupCheckbox.checked ? 0 : 12.99;
deliveryValueEl.textContent = deliveryCost.toFixed(2) + ' PLN';
const totalValue = productsValue + deliveryCost;
totalValueEl.textContent = totalValue.toFixed(2) + ' PLN';
}
async function removeCartItem(bookId) {
try {
const response = await fetch(`/api/remove-from-cart/${bookId}`, {
method: 'DELETE',
credentials: 'include'
});
if (response.ok) {
loadCart();
} else {
alert('Błąd usuwania z koszyka');
}
} catch (error) {
console.error('Błąd:', error);
alert('Wystąpił błąd podczas usuwania z koszyka');
} }
} }
async function handleCheckout() { async function handleCheckout() {
try { const cartItemsContainer = document.getElementById('cart-items');
const token = localStorage.getItem('token'); const localPickupCheckbox = document.getElementById('localPickup');
if (!token) {
window.location.href = '/login.html'; if (!cartItemsContainer || cartItemsContainer.innerHTML.includes('Twój koszyk jest pusty')) {
alert('Twój koszyk jest pusty!');
return; return;
} }
// Pobierz zawartość koszyka try {
const cartResponse = await fetch('/api/cart', { // Pobierz aktualną zawartość koszyka
headers: getAuthHeaders() const response = await fetch('/api/cart', {
credentials: 'include'
}); });
if (!cartResponse.ok) throw new Error('Błąd pobierania koszyka');
const cartItems = await cartResponse.json();
// Wyślij zamówienie if (!response.ok) {
const response = await fetch('/api/checkout', { throw new Error('Błąd ładowania koszyka');
method: 'POST', }
headers: getAuthHeaders(),
body: JSON.stringify({ const cartItems = await response.json();
// Przygotuj dane do zamówienia
const checkoutData = {
items: cartItems.map(item => ({ items: cartItems.map(item => ({
book_id: item.book_id, book_id: item.book_id,
quantity: item.quantity quantity: item.quantity
})), })),
total: cartItems.reduce((sum, item) => total: parseFloat(document.getElementById('total-value').textContent),
sum + (parseFloat(item.cena) * item.quantity), 0) delivery_type: localPickupCheckbox.checked ? "local" : "shipping"
}) };
// Wyślij zamówienie
const checkoutResponse = await fetch('/api/checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(checkoutData),
credentials: 'include'
}); });
if (!response.ok) throw new Error('Błąd podczas składania zamówienia'); if (checkoutResponse.ok) {
window.location.href = '/thankyou.html'; window.location.href = '/thankyou.html';
} else {
const errorData = await checkoutResponse.json();
alert(`Błąd składania zamówienia: ${errorData.message || 'Nieznany błąd'}`);
}
} catch (error) { } catch (error) {
handleApiError(error, 'Nie udało się złożyć zamówienia'); console.error('Błąd:', error);
alert('Wystąpił błąd podczas składania zamówienia');
} }
} }

View file

@ -1,68 +1,80 @@
// static/js/profile.js document.addEventListener('DOMContentLoaded', function() {
loadOrderHistory();
import { getAuthHeaders, handleApiError } from './utils.js';
document.addEventListener('DOMContentLoaded', async () => {
try {
const token = localStorage.getItem('token');
if (!token) {
window.location.href = '/login.html';
return;
}
const response = await fetch('/api/order-history', {
headers: getAuthHeaders()
});
if (!response.ok) throw new Error('Błąd ładowania historii');
const orders = await response.json();
renderOrderHistory(orders);
} catch (error) {
handleApiError(error, 'Nie udało się załadować historii zamówień');
}
}); });
function renderOrderHistory(orders) { async function loadOrderHistory() {
const container = document.getElementById('order-history'); const orderHistoryContainer = document.getElementById('order-history');
container.innerHTML = '';
orders.forEach((order, index) => { if (!orderHistoryContainer) return;
const orderNumber = orders.length - index;
const orderDate = new Date(order.data_zamowienia).toLocaleDateString();
const itemsList = order.items.map(item => ` orderHistoryContainer.innerHTML = `
<div class="order card mb-2"> <div class="text-center py-5">
<div class="card-body"> <div class="spinner-border text-primary" role="status">
<h6 class="card-subtitle mb-2">${item.tytul}</h6> <span class="visually-hidden">Ładowanie...</span>
<p class="card-text">Autor: ${item.autor}</p>
<p class="card-text">Ilość: ${item.ilosc} × ${item.cena} PLN</p>
</div>
</div>
`).join('');
const orderHTML = `
<div class="mb-4">
<div class="card">
<div class="card-header">
<h5 class="card-title">Zamówienie #${orderNumber}</h5>
<p class="card-text">Data: ${orderDate}</p>
</div>
<div class="card-body">
<h6>Pozycje:</h6>
${itemsList}
<hr class="bg-secondary">
<div class="d-flex justify-content-between">
<p class="fw-bold">Suma całkowita:</p>
<p class="fw-bold">${order.suma_totalna} PLN</p>
</div>
<div class="d-flex justify-content-between">
<p>Status:</p>
<p>${order.status || 'Przyjęto do realizacji'}</p>
</div>
</div>
</div> </div>
</div> </div>
`; `;
container.insertAdjacentHTML('beforeend', orderHTML);
try {
const response = await fetch('/api/order-history', {
credentials: 'include'
});
if (!response.ok) {
throw new Error('Błąd ładowania historii zamówień');
}
const orders = await response.json();
displayOrderHistory(orders);
} catch (error) {
console.error('Błąd:', error);
orderHistoryContainer.innerHTML = `
<div class="alert alert-danger">Wystąpił błąd podczas ładowania historii zamówień</div>
`;
}
}
function displayOrderHistory(orders) {
const orderHistoryContainer = document.getElementById('order-history');
if (!orderHistoryContainer) return;
if (orders.length === 0) {
orderHistoryContainer.innerHTML = `
<div class="alert alert-info">Brak historii zamówień</div>
`;
return;
}
orderHistoryContainer.innerHTML = '';
orders.forEach(order => {
const orderDate = new Date(order.data_zamowienia).toLocaleDateString();
const orderElement = `
<div class="card mb-3 order-card">
<div class="card-header">
<h5>Zamówienie #${order.id} - ${orderDate}</h5>
<p class="mb-0">Status: ${order.status || 'W realizacji'}</p>
<p class="mb-0">Suma: ${order.suma_totalna} PLN</p>
<p class="mb-0">Typ dostawy: ${order.typ_dostawy === 'local' ? 'Odbiór lokalny' : 'Dostawa'}</p>
</div>
<div class="card-body">
<h6>Produkty:</h6>
<ul class="list-group">
${order.items.map(item => `
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>${item.tytul}</strong> - ${item.autor}
</div>
<div>
${item.ilosc} x ${item.cena} PLN
</div>
</li>
`).join('')}
</ul>
</div>
</div>
`;
orderHistoryContainer.innerHTML += orderElement;
}); });
} }

46
static/js/theme.js Normal file
View file

@ -0,0 +1,46 @@
// static/js/theme.js
// Eksportuj funkcję setTheme
export function setTheme(theme) {
document.body.classList.remove('dark-theme', 'light-theme');
document.body.classList.add(theme + '-theme');
const themeIcon = document.querySelector('#theme-toggle i');
if (themeIcon) {
themeIcon.className = theme === 'dark'
? 'bi bi-moon-stars-fill'
: 'bi bi-sun-fill';
}
updateThemeColors(theme);
}
function updateThemeColors(theme) {
const primaryColor = theme === 'dark' ? '#1a2e1a' : '#f8f9fa';
const textColor = theme === 'dark' ? '#f5f5f5' : '#212529';
const accentColor = '#d4af37';
document.body.style.backgroundColor = primaryColor;
document.body.style.color = textColor;
const links = document.querySelectorAll('a, .btn');
links.forEach(link => {
link.style.color = accentColor;
});
}
document.addEventListener('DOMContentLoaded', function() {
// Przywróć zapisany motyw
const savedTheme = localStorage.getItem('theme') || 'dark';
setTheme(savedTheme);
// Obsługa przycisku zmiany motywu
const themeToggle = document.getElementById('theme-toggle');
if (themeToggle) {
themeToggle.addEventListener('click', function() {
const currentTheme = document.body.classList.contains('dark-theme') ? 'dark' : 'light';
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
setTheme(newTheme);
localStorage.setItem('theme', newTheme);
});
}
});

View file

@ -1,50 +1,43 @@
export function getAuthHeaders() { // Funkcja do wyświetlania komunikatów
const token = localStorage.getItem('token'); export function showMessage(type, message) {
return { // Utwórz kontener komunikatów jeśli nie istnieje
'Content-Type': 'application/json', let messageContainer = document.getElementById('message-container');
...(token ? { 'Authorization': `Bearer ${token}` } : {}) if (!messageContainer) {
}; messageContainer = document.createElement('div');
} messageContainer.id = 'message-container';
messageContainer.style.position = 'fixed';
export function handleApiError(error, fallbackMsg = 'Wystąpił błąd') { messageContainer.style.top = '20px';
console.error('Error:', error); messageContainer.style.right = '20px';
alert(error.message || fallbackMsg); messageContainer.style.zIndex = '1000';
} document.body.appendChild(messageContainer);
async function updateAuthUI() {
try {
const response = await fetch('/api/check-auth', {
credentials: 'include'
});
if (!response.ok) return;
const data = await response.json();
const authContainers = document.querySelectorAll('.auth-links');
authContainers.forEach(container => {
const anonymousLinks = container.querySelector('.anonymous-links');
const userLinks = container.querySelector('.user-links');
if (data.authenticated) {
if (anonymousLinks) anonymousLinks.style.display = 'none';
if (userLinks) userLinks.style.display = 'flex';
} else {
if (anonymousLinks) anonymousLinks.style.display = 'flex';
if (userLinks) userLinks.style.display = 'none';
}
});
} catch (error) {
console.error('Błąd aktualizacji UI:', error);
} }
// Utwórz element komunikatu
const messageElement = document.createElement('div');
messageElement.className = `alert alert-${type} alert-dismissible fade show`;
messageElement.role = 'alert';
messageElement.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
// Dodaj do kontenera
messageContainer.appendChild(messageElement);
// Automatyczne ukrycie po 5 sekundach
setTimeout(() => {
messageElement.classList.remove('show');
setTimeout(() => messageElement.remove(), 150);
}, 5000);
} }
export function setupLogout() { // Funkcja formatująca walutę
document.getElementById('logoutLink')?.addEventListener('click', (e) => { export function formatCurrency(amount) {
e.preventDefault(); return parseFloat(amount).toFixed(2) + ' PLN';
localStorage.removeItem('token');
window.location.href = '/';
});
} }
document.addEventListener('DOMContentLoaded', updateAuthUI); // Funkcja do pobierania parametrów URL
export function getUrlParam(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}

View file

@ -12,14 +12,16 @@
<link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet"> <link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
</head> </head>
<body class="dark-theme"> <body>
<nav class="navbar navbar-expand-lg"> <nav class="navbar navbar-expand-lg">
<div class="container"> <div class="container">
<div class="d-flex me-lg-3 flex-grow-1"></div> <a class="navbar-brand" href="/">DARK ATHENÆUM</a>
<a class="navbar-brand mx-lg-auto order-lg-1" href="/">DARK ATHENÆUM</a> <div class="d-flex align-items-center ms-auto">
<button id="theme-toggle" class="btn">
<i class="bi bi-moon-stars-fill"></i>
</button>
<div class="d-flex align-items-center order-lg-2">
<div class="auth-links"> <div class="auth-links">
<div class="anonymous-links"> <div class="anonymous-links">
<a class="navbar-link" href="/login.html">Logowanie</a> <a class="navbar-link" href="/login.html">Logowanie</a>
@ -85,5 +87,6 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script type="module" src="/js/utils.js"></script> <script type="module" src="/js/utils.js"></script>
<script type="module" src="/js/auth.js"></script> <script type="module" src="/js/auth.js"></script>
<script type="module" src="/js/theme.js"></script>
</body> </body>
</html> </html>

View file

@ -12,14 +12,16 @@
<link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet"> <link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
</head> </head>
<body class="dark-theme"> <body>
<nav class="navbar navbar-expand-lg"> <nav class="navbar navbar-expand-lg">
<div class="container"> <div class="container">
<div class="d-flex me-lg-3 flex-grow-1"></div> <a class="navbar-brand" href="/">DARK ATHENÆUM</a>
<a class="navbar-brand mx-lg-auto order-lg-1" href="/">DARK ATHENÆUM</a> <div class="d-flex align-items-center ms-auto">
<button id="theme-toggle" class="btn">
<i class="bi bi-moon-stars-fill"></i>
</button>
<div class="d-flex align-items-center order-lg-2">
<div class="auth-links"> <div class="auth-links">
<div class="anonymous-links"> <div class="anonymous-links">
<a class="navbar-link" href="/login.html">Logowanie</a> <a class="navbar-link" href="/login.html">Logowanie</a>
@ -37,11 +39,10 @@
</div> </div>
</nav> </nav>
<main class="container my-5"> <main class="container my-5">
<h1 class="mb-4 fw-bold">Twój profil</h1> <h1 class="mb-4 fw-bold">Twój profil</h1>
<div class="card dark-card p-4 mb-4"> <div class="card p-4 mb-4">
<h3 class="mb-3 border-bottom pb-2">Historia zamówień</h3> <h3 class="mb-3 border-bottom pb-2">Historia zamówień</h3>
<div id="order-history" class="mt-3"></div> <div id="order-history" class="mt-3"></div>
</div> </div>
@ -69,6 +70,7 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script type="module" src="/js/utils.js"></script> <script type="module" src="/js/utils.js"></script>
<script type="module" src="/js/auth.js"></script> <script type="module" src="/js/auth.js"></script>
<script type="module" src="/js/theme.js"></script>
<script type="module" src="/js/profile.js"></script> <script type="module" src="/js/profile.js"></script>
</body> </body>
</html> </html>

View file

@ -12,14 +12,16 @@
<link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet"> <link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
</head> </head>
<body class="dark-theme"> <body>
<nav class="navbar navbar-expand-lg"> <nav class="navbar navbar-expand-lg">
<div class="container"> <div class="container">
<div class="d-flex me-lg-3 flex-grow-1"></div> <a class="navbar-brand" href="/">DARK ATHENÆUM</a>
<a class="navbar-brand mx-lg-auto order-lg-1" href="/">DARK ATHENÆUM</a> <div class="d-flex align-items-center ms-auto">
<button id="theme-toggle" class="btn">
<i class="bi bi-moon-stars-fill"></i>
</button>
<div class="d-flex align-items-center order-lg-2">
<div class="auth-links"> <div class="auth-links">
<div class="anonymous-links"> <div class="anonymous-links">
<a class="navbar-link" href="/login.html">Logowanie</a> <a class="navbar-link" href="/login.html">Logowanie</a>
@ -93,5 +95,6 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script type="module" src="/js/utils.js"></script> <script type="module" src="/js/utils.js"></script>
<script type="module" src="/js/auth.js"></script> <script type="module" src="/js/auth.js"></script>
<script type="module" src="/js/theme.js"></script>
</body> </body>
</html> </html>

View file

@ -12,14 +12,16 @@
<link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet"> <link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
</head> </head>
<body class="dark-theme"> <body>
<nav class="navbar navbar-expand-lg"> <nav class="navbar navbar-expand-lg">
<div class="container"> <div class="container">
<div class="d-flex me-lg-3 flex-grow-1"></div> <a class="navbar-brand" href="/">DARK ATHENÆUM</a>
<a class="navbar-brand mx-lg-auto order-lg-1" href="/">DARK ATHENÆUM</a> <div class="d-flex align-items-center ms-auto">
<button id="theme-toggle" class="btn">
<i class="bi bi-moon-stars-fill"></i>
</button>
<div class="d-flex align-items-center order-lg-2">
<div class="auth-links"> <div class="auth-links">
<div class="user-links"> <div class="user-links">
<a class="navbar-link" href="/profile.html">Profil</a> <a class="navbar-link" href="/profile.html">Profil</a>
@ -73,5 +75,6 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script type="module" src="/js/utils.js"></script> <script type="module" src="/js/utils.js"></script>
<script type="module" src="/js/auth.js"></script> <script type="module" src="/js/auth.js"></script>
<script type="module" src="/js/theme.js"></script>
</body> </body>
</html> </html>