polish #6
This commit is contained in:
parent
36c36c8afb
commit
e19f942547
19 changed files with 1270 additions and 682 deletions
1
migrations/20250531163129_dostawa.sql
Normal file
1
migrations/20250531163129_dostawa.sql
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE zamowienia ADD COLUMN typ_dostawy VARCHAR(20) NOT NULL DEFAULT 'shipping';
|
42
src/cart.rs
42
src/cart.rs
|
@ -7,6 +7,7 @@ use bigdecimal::BigDecimal;
|
|||
use serde_json::json;
|
||||
use log;
|
||||
use std::str::FromStr; // Dodane
|
||||
use crate::models::CartQuantityUpdate;
|
||||
|
||||
#[get("/api/cart")]
|
||||
pub async fn get_cart(
|
||||
|
@ -110,20 +111,22 @@ pub async fn checkout(
|
|||
let total_bigdecimal = BigDecimal::from_str(&total_str)
|
||||
.map_err(|_| AppError::BadRequest("Invalid total value".to_string()))?;
|
||||
|
||||
// 1. Utwórz zamówienie
|
||||
let order_id = sqlx::query!(
|
||||
"INSERT INTO zamowienia (user_id, suma_totalna)
|
||||
VALUES ($1, $2) RETURNING id",
|
||||
// 1. Utwórz zamówienie z typem dostawy
|
||||
let order_record = sqlx::query!(
|
||||
"INSERT INTO zamowienia (user_id, suma_totalna, typ_dostawy)
|
||||
VALUES ($1, $2, $3) RETURNING id",
|
||||
user_id,
|
||||
total_bigdecimal
|
||||
total_bigdecimal,
|
||||
data.delivery_type
|
||||
)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("Błąd tworzenia zamówienia: {}", e);
|
||||
AppError::InternalServerError("Błąd serwera".to_string())
|
||||
})?
|
||||
.id;
|
||||
})?;
|
||||
|
||||
let order_id = order_record.id;
|
||||
|
||||
// 2. Dodaj pozycje zamówienia
|
||||
for item in &data.items {
|
||||
|
@ -173,3 +176,28 @@ pub async fn checkout(
|
|||
|
||||
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"})))
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ async fn main() -> std::io::Result<()> {
|
|||
.service(cart::add_to_cart)
|
||||
.service(cart::remove_from_cart)
|
||||
.service(cart::checkout)
|
||||
.service(cart::update_cart_quantity)
|
||||
.service(profile::get_order_history)
|
||||
.service(
|
||||
Files::new("/images", "./static/images")
|
||||
|
|
|
@ -67,9 +67,15 @@ pub struct OrderWithItems {
|
|||
pub items: Vec<OrderItem>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CartQuantityUpdate {
|
||||
pub book_id: i32,
|
||||
pub change: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CheckoutRequest {
|
||||
pub items: Vec<CartItem>,
|
||||
pub total: f64,
|
||||
pub delivery_type: String, // "shipping" lub "local"
|
||||
}
|
||||
|
||||
|
|
|
@ -12,20 +12,16 @@
|
|||
<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">
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container">
|
||||
<form class="d-flex me-lg-3 flex-grow-1" id="searchForm">
|
||||
<input class="form-control me-2 dark-input"
|
||||
type="search"
|
||||
placeholder="Szukaj książek..."
|
||||
aria-label="Search"
|
||||
id="searchInput">
|
||||
</form>
|
||||
<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="anonymous-links">
|
||||
<a class="navbar-link" href="/login.html">Logowanie</a>
|
||||
|
@ -44,13 +40,13 @@
|
|||
</nav>
|
||||
|
||||
<main class="container my-5">
|
||||
<div class="row" id="book-details">
|
||||
<div class="col-md-4">
|
||||
<div class="row g-4" id="book-details">
|
||||
<div class="col-lg-4 col-md-5">
|
||||
<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 class="col-md-8">
|
||||
<div class="col-lg-8 col-md-7">
|
||||
<h1 id="book-title" class="mb-3 fw-bold"></h1>
|
||||
<h3 id="book-author" class="mb-4 text-muted"></h3>
|
||||
<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 type="module" src="/js/utils.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>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -12,20 +12,16 @@
|
|||
<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">
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container">
|
||||
<form class="d-flex me-lg-3 flex-grow-1" id="searchForm">
|
||||
<input class="form-control me-2 dark-input"
|
||||
type="search"
|
||||
placeholder="Szukaj książek..."
|
||||
aria-label="Search"
|
||||
id="searchInput">
|
||||
</form>
|
||||
<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="user-links">
|
||||
<a class="navbar-link" href="/profile.html">Profil</a>
|
||||
|
@ -61,10 +57,20 @@
|
|||
<span>Wartość produktów:</span>
|
||||
<span class="text-primary" id="products-value">0.00 PLN</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<span>Dostawa:</span>
|
||||
<span>12.99 PLN</span>
|
||||
|
||||
<!-- Zmieniony układ dla opcji dostawy -->
|
||||
<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>
|
||||
<span id="delivery-value">12.99 PLN</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between fw-bold fs-5">
|
||||
<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 type="module" src="/js/utils.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>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,80 +1,272 @@
|
|||
/* static/css/styles.css */
|
||||
|
||||
/* Główne zmienne kolorystyczne */
|
||||
:root {
|
||||
--dark-bg: #121212;
|
||||
--darker-bg: #0a0a0a;
|
||||
--light-text: #f8f9fa;
|
||||
--muted-text: #adb5bd;
|
||||
--primary: #d4af37; /* Złoty - główny kolor */
|
||||
--primary-dark: #b38f2d; /* Ciemniejszy złoty */
|
||||
--secondary: #0a1f0a; /* Ciemnozielony - drugi kolor */
|
||||
--border: #343a40;
|
||||
--accent: #228B22; /* Zielony akcent */
|
||||
--dark-primary: #1a2e1a; /* ciemnozielony */
|
||||
--light-primary: #f8f9fa; /* jasny */
|
||||
--gold: #d4af37; /* złoty */
|
||||
--dark-text: #f5f5f5;
|
||||
--light-text: #212529;
|
||||
--dark-secondary: #0d1c0d;
|
||||
--light-secondary: #e9ecef;
|
||||
--dark-card: #142814;
|
||||
--light-card: #ffffff;
|
||||
}
|
||||
|
||||
.dark-theme {
|
||||
background-color: var(--dark-bg);
|
||||
color: var(--light-text);
|
||||
/* Reset i podstawowe style */
|
||||
* {
|
||||
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;
|
||||
display: flex;
|
||||
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 {
|
||||
background-color: #000;
|
||||
padding: 1rem 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-family: 'Old English Text MT', serif;
|
||||
font-size: 2.5rem;
|
||||
color: var(--primary) !important;
|
||||
text-shadow: 0 0 5px rgba(212, 175, 55, 0.5);
|
||||
font-size: 2rem;
|
||||
color: var(--gold) !important;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.navbar-link {
|
||||
color: var(--muted-text) !important;
|
||||
color: var(--gold) !important;
|
||||
text-decoration: none;
|
||||
margin: 0 0.8rem;
|
||||
transition: color 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 0.5rem;
|
||||
transition: opacity 0.3s ease;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.navbar-link:hover, .navbar-link:focus {
|
||||
color: var(--primary) !important;
|
||||
.navbar-link:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.navbar-link i {
|
||||
margin-right: 0.3rem;
|
||||
.btn {
|
||||
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 */
|
||||
.book-card {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--darker-bg);
|
||||
border: 1px solid var(--border);
|
||||
.btn:hover {
|
||||
background-color: var(--gold);
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.btn-add-to-cart {
|
||||
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;
|
||||
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);
|
||||
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;
|
||||
padding-bottom: 160%; /* 5:8 proportion (5/8 = 0.625) */
|
||||
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 {
|
||||
|
@ -87,134 +279,184 @@
|
|||
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);
|
||||
}
|
||||
|
||||
.book-body {
|
||||
padding: 1.2rem;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* Responsywność */
|
||||
@media (max-width: 1200px) {
|
||||
.books-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.book-title {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--light-text);
|
||||
font-weight: 600;
|
||||
min-height: 2.8em;
|
||||
@media (max-width: 992px) {
|
||||
.books-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.book-author {
|
||||
font-size: 0.9rem;
|
||||
color: var(--muted-text);
|
||||
margin-bottom: 0.5rem;
|
||||
@media (max-width: 768px) {
|
||||
.books-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.book-price {
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
margin-top: auto;
|
||||
font-size: 1.2rem;
|
||||
@media (max-width: 576px) {
|
||||
.books-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Przyciski i elementy interfejsu */
|
||||
.btn {
|
||||
background-color: var(--primary);
|
||||
border: none;
|
||||
color: #000;
|
||||
padding: 0.8rem 1.5rem;
|
||||
border-radius: 5px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
@media (max-width: 768px) {
|
||||
.book-cover-container {
|
||||
padding-top: 140%; /* Slightly different ratio on mobile */
|
||||
}
|
||||
}
|
||||
|
||||
/* Koszyk */
|
||||
#cart-items .card {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: var(--primary-dark);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
background-color: var(--primary) !important;
|
||||
/* Animacje dla ilości */
|
||||
.quantity-change {
|
||||
transition: opacity 0.15s ease, transform 0.15s ease;
|
||||
}
|
||||
|
||||
/* Formularze */
|
||||
.auth-container {
|
||||
max-width: 500px;
|
||||
margin: 3rem auto;
|
||||
padding: 2.5rem;
|
||||
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);
|
||||
.form-control, .form-select {
|
||||
border-radius: 0;
|
||||
padding: 0.75rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Responsywność */
|
||||
@media (max-width: 768px) {
|
||||
.navbar-brand {
|
||||
font-size: 1.8rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.cover-container {
|
||||
padding-bottom: 130%;
|
||||
.books-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
}
|
||||
|
||||
.book-title {
|
||||
min-height: auto;
|
||||
.row.mb-4 {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -15,11 +15,13 @@
|
|||
<body class="dark-theme">
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<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="anonymous-links">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Wyszukiwanie przeniesione obok sortowania -->
|
||||
<div class="col-md-5">
|
||||
<input class="form-control dark-input"
|
||||
type="search"
|
||||
|
@ -64,7 +65,7 @@
|
|||
</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="spinner-border text-primary" role="status">
|
||||
<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 type="module" src="/js/utils.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>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -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();
|
||||
|
||||
const email = document.getElementById('loginEmail').value;
|
||||
const password = document.getElementById('loginPassword').value;
|
||||
|
||||
try {
|
||||
const response = await fetch('/login', {
|
||||
method: 'POST',
|
||||
credentials: 'include', // dołącz ciasteczka
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
email: document.getElementById('loginEmail').value,
|
||||
haslo: document.getElementById('loginPassword').value
|
||||
})
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email, haslo: password }),
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
localStorage.setItem('userName', data.imie);
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
alert('Błąd logowania!');
|
||||
const errorData = await response.json();
|
||||
alert(`Błąd logowania: ${errorData.message || 'Nieznany błąd'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Błąd:', error);
|
||||
console.error('Błąd logowania:', error);
|
||||
alert('Wystąpił błąd podczas logowania');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Rejestracja
|
||||
document.getElementById('registerForm')?.addEventListener('submit', async (e) => {
|
||||
async function handleRegister(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const name = document.getElementById('registerName').value;
|
||||
const email = document.getElementById('registerEmail').value;
|
||||
const password = document.getElementById('registerPassword').value;
|
||||
const confirmPassword = document.getElementById('registerConfirmPassword').value;
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
alert('Hasła nie są identyczne');
|
||||
alert('Hasła nie są identyczne!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/rejestracja', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ imie: name, email, haslo: password, confirmPassword }),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
haslo: password,
|
||||
imie: name,
|
||||
confirm_password: confirmPassword
|
||||
}),
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
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';
|
||||
} else {
|
||||
throw new Error('Rejestracja nieudana');
|
||||
const errorData = await response.json();
|
||||
alert(`Błąd rejestracji: ${errorData.message || 'Nieznany błąd'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
handleApiError(error);
|
||||
}
|
||||
});
|
||||
|
||||
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 };
|
||||
console.error('Błąd rejestracji:', error);
|
||||
alert('Wystąpił błąd podczas rejestracji');
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('logoutLink')?.addEventListener('click', async (e) => {
|
||||
async function handleLogout(e) {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
await fetch('/api/logout', {
|
||||
const response = await fetch('/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include' // ważne - dołącz ciasteczka
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
localStorage.removeItem('userName');
|
||||
window.location.href = '/';
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
} else {
|
||||
alert('Wystąpił problem podczas wylogowywania');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Błąd wylogowywania:', error);
|
||||
alert('Wystąpił błąd podczas wylogowywania');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,68 +1,77 @@
|
|||
// static/js/book.js
|
||||
|
||||
import { getAuthHeaders, handleApiError } from './utils.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const bookId = urlParams.get('id');
|
||||
const bookDetails = document.getElementById('book-details');
|
||||
|
||||
if (!bookId) {
|
||||
bookDetails.innerHTML = `
|
||||
<div class="col-12 text-center py-5">
|
||||
<h2 class="text-danger">Nieprawidłowe ID książki</h2>
|
||||
<a href="/" class="btn mt-3">Powrót do strony głównej</a>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
if (bookId) {
|
||||
loadBookDetails(bookId);
|
||||
|
||||
// Dodaj obsługę przycisku "Dodaj do koszyka"
|
||||
const addToCartBtn = document.querySelector('.btn-add-to-cart');
|
||||
if (addToCartBtn) {
|
||||
addToCartBtn.addEventListener('click', () => addToCart(bookId));
|
||||
}
|
||||
|
||||
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 {
|
||||
throw new Error('Wystąpił błąd');
|
||||
}
|
||||
} 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>
|
||||
document.getElementById('book-details').innerHTML = `
|
||||
<div class="alert alert-danger">Nie znaleziono książki</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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +1,137 @@
|
|||
// static/js/books.js
|
||||
|
||||
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
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const booksContainer = document.getElementById('books-container');
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const sortSelect = document.getElementById('sortSelect');
|
||||
|
||||
const reloadBooks = async () => {
|
||||
const books = await loadBooks(
|
||||
searchInput?.value || '',
|
||||
sortSelect?.value || 'default'
|
||||
);
|
||||
renderBooks(books, 'books-container');
|
||||
};
|
||||
// Załaduj książki przy starcie
|
||||
loadBooks();
|
||||
|
||||
searchInput?.addEventListener('input', reloadBooks);
|
||||
sortSelect?.addEventListener('change', reloadBooks);
|
||||
// Dodaj event listeners
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
window.location.href = '/login.html';
|
||||
return;
|
||||
}
|
||||
// Dodaj event listeners
|
||||
if (localPickupCheckbox) {
|
||||
// JEDEN listener dla checkboxa
|
||||
localPickupCheckbox.addEventListener('change', function() {
|
||||
// Pobierz aktualny stan koszyka i zaktualizuj podsumowanie
|
||||
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', {
|
||||
headers: getAuthHeaders()
|
||||
return {
|
||||
book_id: parseInt(bookId),
|
||||
quantity,
|
||||
cena,
|
||||
tytul,
|
||||
obraz_url
|
||||
};
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Błąd ładowania koszyka');
|
||||
const cartItems = await response.json();
|
||||
renderCart(cartItems);
|
||||
updateSummary(cartItems);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
handleApiError(error, 'Nie udało się załadować koszyka');
|
||||
if (checkoutBtn) {
|
||||
checkoutBtn.addEventListener('click', handleCheckout);
|
||||
}
|
||||
});
|
||||
|
||||
function renderCart(cartItems) {
|
||||
const container = document.getElementById('cart-items');
|
||||
container.innerHTML = '';
|
||||
async function loadCart() {
|
||||
const cartItemsContainer = document.getElementById('cart-items');
|
||||
|
||||
if (cartItems.length === 0) {
|
||||
container.innerHTML = '<p class="text-center">Twój koszyk jest pusty</p>';
|
||||
return;
|
||||
if (!cartItemsContainer) 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;
|
||||
|
||||
cartItems.forEach(item => {
|
||||
const price = parseFloat(item.cena);
|
||||
const itemTotal = price * item.quantity;
|
||||
totalCartValue += itemTotal;
|
||||
|
||||
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>
|
||||
const cartItems = await response.json();
|
||||
displayCartItems(cartItems);
|
||||
updateSummary(cartItems);
|
||||
} catch (error) {
|
||||
console.error('Błąd ładowania koszyka:', error);
|
||||
cartItemsContainer.innerHTML = `
|
||||
<div class="alert alert-danger">Wystąpił błąd podczas ładowania koszyka: ${error.message}</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) {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
window.location.href = '/login.html';
|
||||
function displayCartItems(cartItems) {
|
||||
const cartItemsContainer = document.getElementById('cart-items');
|
||||
|
||||
if (!cartItemsContainer) return;
|
||||
|
||||
if (cartItems.length === 0) {
|
||||
cartItemsContainer.innerHTML = `
|
||||
<div class="alert alert-info">
|
||||
Twój koszyk jest pusty
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
let method, endpoint;
|
||||
if (action === 'decrease') {
|
||||
endpoint = `/api/decrease-cart-item/${bookId}`;
|
||||
method = 'POST';
|
||||
} else {
|
||||
endpoint = `/api/remove-from-cart/${bookId}`;
|
||||
method = 'DELETE';
|
||||
}
|
||||
cartItemsContainer.innerHTML = '';
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
method,
|
||||
headers: getAuthHeaders()
|
||||
cartItems.forEach(item => {
|
||||
const itemElement = `
|
||||
<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');
|
||||
location.reload();
|
||||
// Dodaj event listeners dla przycisków
|
||||
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) {
|
||||
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() {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
window.location.href = '/login.html';
|
||||
const cartItemsContainer = document.getElementById('cart-items');
|
||||
const localPickupCheckbox = document.getElementById('localPickup');
|
||||
|
||||
if (!cartItemsContainer || cartItemsContainer.innerHTML.includes('Twój koszyk jest pusty')) {
|
||||
alert('Twój koszyk jest pusty!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Pobierz zawartość koszyka
|
||||
const cartResponse = await fetch('/api/cart', {
|
||||
headers: getAuthHeaders()
|
||||
try {
|
||||
// Pobierz aktualną zawartość koszyka
|
||||
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
|
||||
const response = await fetch('/api/checkout', {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({
|
||||
if (!response.ok) {
|
||||
throw new Error('Błąd ładowania koszyka');
|
||||
}
|
||||
|
||||
const cartItems = await response.json();
|
||||
|
||||
// Przygotuj dane do zamówienia
|
||||
const checkoutData = {
|
||||
items: cartItems.map(item => ({
|
||||
book_id: item.book_id,
|
||||
quantity: item.quantity
|
||||
})),
|
||||
total: cartItems.reduce((sum, item) =>
|
||||
sum + (parseFloat(item.cena) * item.quantity), 0)
|
||||
})
|
||||
total: parseFloat(document.getElementById('total-value').textContent),
|
||||
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';
|
||||
} else {
|
||||
const errorData = await checkoutResponse.json();
|
||||
alert(`Błąd składania zamówienia: ${errorData.message || 'Nieznany błąd'}`);
|
||||
}
|
||||
} 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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,68 +1,80 @@
|
|||
// static/js/profile.js
|
||||
|
||||
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ń');
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadOrderHistory();
|
||||
});
|
||||
|
||||
function renderOrderHistory(orders) {
|
||||
const container = document.getElementById('order-history');
|
||||
container.innerHTML = '';
|
||||
async function loadOrderHistory() {
|
||||
const orderHistoryContainer = document.getElementById('order-history');
|
||||
|
||||
orders.forEach((order, index) => {
|
||||
const orderNumber = orders.length - index;
|
||||
const orderDate = new Date(order.data_zamowienia).toLocaleDateString();
|
||||
if (!orderHistoryContainer) return;
|
||||
|
||||
const itemsList = order.items.map(item => `
|
||||
<div class="order card mb-2">
|
||||
<div class="card-body">
|
||||
<h6 class="card-subtitle mb-2">${item.tytul}</h6>
|
||||
<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>
|
||||
orderHistoryContainer.innerHTML = `
|
||||
<div class="text-center py-5">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Ładowanie...</span>
|
||||
</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
46
static/js/theme.js
Normal 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);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -1,50 +1,43 @@
|
|||
export function getAuthHeaders() {
|
||||
const token = localStorage.getItem('token');
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
};
|
||||
}
|
||||
|
||||
export function handleApiError(error, fallbackMsg = 'Wystąpił błąd') {
|
||||
console.error('Error:', error);
|
||||
alert(error.message || fallbackMsg);
|
||||
}
|
||||
|
||||
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);
|
||||
// Funkcja do wyświetlania komunikatów
|
||||
export function showMessage(type, message) {
|
||||
// Utwórz kontener komunikatów jeśli nie istnieje
|
||||
let messageContainer = document.getElementById('message-container');
|
||||
if (!messageContainer) {
|
||||
messageContainer = document.createElement('div');
|
||||
messageContainer.id = 'message-container';
|
||||
messageContainer.style.position = 'fixed';
|
||||
messageContainer.style.top = '20px';
|
||||
messageContainer.style.right = '20px';
|
||||
messageContainer.style.zIndex = '1000';
|
||||
document.body.appendChild(messageContainer);
|
||||
}
|
||||
|
||||
// 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() {
|
||||
document.getElementById('logoutLink')?.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
localStorage.removeItem('token');
|
||||
window.location.href = '/';
|
||||
});
|
||||
// Funkcja formatująca walutę
|
||||
export function formatCurrency(amount) {
|
||||
return parseFloat(amount).toFixed(2) + ' PLN';
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -12,14 +12,16 @@
|
|||
<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">
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<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="anonymous-links">
|
||||
<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 type="module" src="/js/utils.js"></script>
|
||||
<script type="module" src="/js/auth.js"></script>
|
||||
<script type="module" src="/js/theme.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -12,14 +12,16 @@
|
|||
<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">
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<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="anonymous-links">
|
||||
<a class="navbar-link" href="/login.html">Logowanie</a>
|
||||
|
@ -37,11 +39,10 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
<main class="container my-5">
|
||||
<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>
|
||||
<div id="order-history" class="mt-3"></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 type="module" src="/js/utils.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>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -12,14 +12,16 @@
|
|||
<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">
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<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="anonymous-links">
|
||||
<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 type="module" src="/js/utils.js"></script>
|
||||
<script type="module" src="/js/auth.js"></script>
|
||||
<script type="module" src="/js/theme.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -12,14 +12,16 @@
|
|||
<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">
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<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="user-links">
|
||||
<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 type="module" src="/js/utils.js"></script>
|
||||
<script type="module" src="/js/auth.js"></script>
|
||||
<script type="module" src="/js/theme.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Add table
Reference in a new issue