done homepage
This commit is contained in:
parent
154dabfee1
commit
bdad6fdedd
15 changed files with 606 additions and 94 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1414,6 +1414,7 @@ dependencies = [
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
|
"regex",
|
||||||
"rust_decimal",
|
"rust_decimal",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
@ -17,3 +17,4 @@ log = "0.4"
|
||||||
bcrypt = "0.15"
|
bcrypt = "0.15"
|
||||||
rust_decimal = { version = "1.37.1", features = ["serde", "db-postgres"] }
|
rust_decimal = { version = "1.37.1", features = ["serde", "db-postgres"] }
|
||||||
bigdecimal = { version = "0.3.0", features = ["serde"] }
|
bigdecimal = { version = "0.3.0", features = ["serde"] }
|
||||||
|
regex = "1.10.4"
|
||||||
|
|
1
migrations/20250523170606_opis.sql
Normal file
1
migrations/20250523170606_opis.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
-- Add migration script here
|
4
migrations/20250523173412_add_opis_column_to_ksiazki.sql
Normal file
4
migrations/20250523173412_add_opis_column_to_ksiazki.sql
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
ALTER TABLE ksiazki
|
||||||
|
ADD COLUMN opis TEXT;
|
||||||
|
|
||||||
|
-- Add migration script here
|
|
@ -1,2 +0,0 @@
|
||||||
ALTER TABLE ksiazki
|
|
||||||
ALTER COLUMN cena TYPE NUMERIC(10,2);
|
|
107
src/main.rs
107
src/main.rs
|
@ -11,6 +11,8 @@ use sqlx::postgres::PgPoolOptions;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
use bigdecimal::BigDecimal;
|
use bigdecimal::BigDecimal;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::From;
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, serde::Serialize)]
|
#[derive(sqlx::FromRow, serde::Serialize)]
|
||||||
struct Book {
|
struct Book {
|
||||||
|
@ -18,26 +20,107 @@ struct Book {
|
||||||
tytul: String,
|
tytul: String,
|
||||||
autor: String,
|
autor: String,
|
||||||
cena: BigDecimal,
|
cena: BigDecimal,
|
||||||
obraz_url: Option<String>
|
obraz_url: Option<String>,
|
||||||
|
opis: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/ksiazki")]
|
#[get("/api/ksiazki")]
|
||||||
async fn get_ksiazki(pool: web::Data<sqlx::PgPool>) -> impl Responder {
|
async fn get_ksiazki(
|
||||||
match sqlx::query_as!(
|
pool: web::Data<sqlx::PgPool>,
|
||||||
Book,
|
web::Query(params): web::Query<HashMap<String, String>>,
|
||||||
r#"SELECT id, tytul, autor, cena, obraz_url FROM ksiazki"#
|
) -> impl Responder {
|
||||||
)
|
let search_term = params.get("search").map(|s| s.as_str()).unwrap_or("");
|
||||||
.fetch_all(pool.get_ref())
|
let sort_by = params.get("sort").map(|s| s.as_str()).unwrap_or("default");
|
||||||
.await
|
|
||||||
{
|
let base_query = "SELECT
|
||||||
|
id,
|
||||||
|
tytul,
|
||||||
|
autor,
|
||||||
|
cena,
|
||||||
|
COALESCE('/images/' || obraz_url, '/images/placeholder.jpg') as obraz_url,
|
||||||
|
COALESCE(opis, 'Brak opisu') as opis
|
||||||
|
FROM ksiazki";
|
||||||
|
|
||||||
|
let sort_clause = match sort_by {
|
||||||
|
"price_asc" => " ORDER BY cena ASC",
|
||||||
|
"price_desc" => " ORDER BY cena DESC",
|
||||||
|
"title_asc" => " ORDER BY tytul ASC",
|
||||||
|
"author_asc" => " ORDER BY autor ASC",
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
|
||||||
|
let query = if !search_term.is_empty() {
|
||||||
|
format!(
|
||||||
|
"{} WHERE LOWER(tytul) LIKE LOWER($1) OR LOWER(autor) LIKE LOWER($1){}",
|
||||||
|
base_query, sort_clause
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("{}{}", base_query, sort_clause)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut query_builder = sqlx::query_as::<_, Book>(&query);
|
||||||
|
|
||||||
|
if !search_term.is_empty() {
|
||||||
|
query_builder = query_builder.bind(format!("%{}%", search_term));
|
||||||
|
}
|
||||||
|
|
||||||
|
match query_builder.fetch_all(pool.get_ref()).await {
|
||||||
Ok(books) => HttpResponse::Ok().json(books),
|
Ok(books) => HttpResponse::Ok().json(books),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Błąd bazy danych: {:?}", e);
|
log::error!("Błąd bazy danych: {:?}", e);
|
||||||
HttpResponse::InternalServerError().body(format!("Błąd: {}", e))
|
HttpResponse::InternalServerError().json(json!({"error": "Błąd serwera"}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#[get("/api/ksiazki")]
|
||||||
|
//async fn get_ksiazki(pool: web::Data<sqlx::PgPool>) -> impl Responder {
|
||||||
|
// match sqlx::query_as!(
|
||||||
|
// Book,
|
||||||
|
// r#"SELECT id, tytul, autor, cena, obraz_url FROM ksiazki"#
|
||||||
|
// )
|
||||||
|
// .fetch_all(pool.get_ref())
|
||||||
|
// .await
|
||||||
|
// {
|
||||||
|
// Ok(books) => HttpResponse::Ok().json(books),
|
||||||
|
// Err(e) => {
|
||||||
|
// log::error!("Błąd bazy danych: {:?}", e);
|
||||||
|
// HttpResponse::InternalServerError().body(format!("Błąd: {}", e))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
#[get("/api/ksiazki/{id}")]
|
||||||
|
async fn get_ksiazka(
|
||||||
|
pool: web::Data<sqlx::PgPool>,
|
||||||
|
path: web::Path<i32>,
|
||||||
|
) -> impl Responder {
|
||||||
|
let id = path.into_inner();
|
||||||
|
|
||||||
|
match sqlx::query_as!(
|
||||||
|
Book,
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
tytul,
|
||||||
|
autor,
|
||||||
|
cena,
|
||||||
|
COALESCE('/images/' || obraz_url, '/images/placeholder.jpg') as obraz_url,
|
||||||
|
opis
|
||||||
|
FROM ksiazki
|
||||||
|
WHERE id = $1
|
||||||
|
"#,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_optional(pool.get_ref())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Some(book)) => HttpResponse::Ok().json(book),
|
||||||
|
Ok(None) => HttpResponse::NotFound().finish(),
|
||||||
|
Err(e) => HttpResponse::InternalServerError().body(format!("Błąd: {}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/api/check-auth")]
|
#[get("/api/check-auth")]
|
||||||
async fn check_auth() -> impl Responder {
|
async fn check_auth() -> impl Responder {
|
||||||
HttpResponse::Ok().json(json!({
|
HttpResponse::Ok().json(json!({
|
||||||
|
@ -81,6 +164,10 @@ async fn main() -> std::io::Result<()> {
|
||||||
.service(get_ksiazki)
|
.service(get_ksiazki)
|
||||||
.service(auth::rejestracja)
|
.service(auth::rejestracja)
|
||||||
.service(auth::login)
|
.service(auth::login)
|
||||||
|
.service(
|
||||||
|
Files::new("/images", "./static/images") // Nowy endpoint dla obrazków
|
||||||
|
.show_files_listing(),
|
||||||
|
)
|
||||||
.service(Files::new("/", "./static").index_file("index.html"))
|
.service(Files::new("/", "./static").index_file("index.html"))
|
||||||
})
|
})
|
||||||
.bind("0.0.0.0:7999")?
|
.bind("0.0.0.0:7999")?
|
||||||
|
|
83
static/book.html
Normal file
83
static/book.html
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Dark Athenaeum</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="/css/styles.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Lobster&family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="dark-theme">
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-black">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="/">DARK ATHENAEUM</a>
|
||||||
|
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse justify-content-end" id="navbarNav">
|
||||||
|
<div class="navbar-nav gap-3">
|
||||||
|
<!-- Dodaj pole wyszukiwania -->
|
||||||
|
<form class="d-flex" id="searchForm">
|
||||||
|
<input class="form-control me-2 dark-input" type="search" placeholder="Szukaj książek..." aria-label="Search">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Linki -->
|
||||||
|
<div class="anonymous-links d-flex gap-3">
|
||||||
|
<a class="nav-link" href="/login.html">Logowanie</a>
|
||||||
|
<a class="nav-link" href="/register.html">Rejestracja</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-links d-flex gap-3" style="display: none;">
|
||||||
|
<a class="nav-link" href="/profile.html">Profil</a>
|
||||||
|
<a class="nav-link" href="#" id="logoutLink">Wyloguj</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a class="nav-link" href="/cart.html">
|
||||||
|
<i class="bi bi-basket"></i> Koszyk
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="container py-5">
|
||||||
|
<div class="row" id="book-details">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<img id="book-cover" class="img-fluid mb-4" alt="Okładka książki">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h1 id="book-title" class="lobster-font neon-title mb-4"></h1>
|
||||||
|
<h3 id="book-author" class="inter-font text-accent mb-3"></h3>
|
||||||
|
<p id="book-price" class="inter-font fs-4 mb-4"></p>
|
||||||
|
<div class="card dark-card mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="lobster-font mb-3">Opis</h4>
|
||||||
|
<p id="book-description" class="inter-font"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-gothic btn-lg">Dodaj do koszyka</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="mt-5">
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<h5 class="lobster-font"><a href="https://sykorax.eu/">O Nas</a></h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<h5 class="lobster-font"><a href="https://sykorax.eu/">Kontakt</a></h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<h5 class="lobster-font"><a href="https://sykorax.eu/">Współpraca</a></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="/js/book.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -64,3 +64,132 @@ footer {
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#books-container {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-card {
|
||||||
|
max-width: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-cover {
|
||||||
|
height: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-cover img {
|
||||||
|
object-fit: contain;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-overlay {
|
||||||
|
background: linear-gradient(0deg, rgba(7,44,36,0.9) 0%, rgba(7,44,36,0.7) 100%);
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-cover:hover .book-overlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-info {
|
||||||
|
color: var(--text-gold);
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dodaj do istniejących styli */
|
||||||
|
.lobster-font {
|
||||||
|
font-family: 'Lobster', cursive;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inter-font {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: var(--accent-blue) !important;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-accent {
|
||||||
|
color: var(--accent-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-card {
|
||||||
|
background: #09342b;
|
||||||
|
border: 1px solid #1c4d42;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-accent {
|
||||||
|
color: #78DBFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
#book-description {
|
||||||
|
line-height: 1.8;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchInput {
|
||||||
|
height: 50px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* W pliku styles.css */
|
||||||
|
.navbar-nav {
|
||||||
|
gap: 1.5rem !important; /* Odstępy między linkami */
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
padding: 0.5rem 1rem; /* Większy obszar klikalny */
|
||||||
|
display: inline-block; /* Lepsze wyrównanie */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-img-top {
|
||||||
|
height: 350px;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-dark {
|
||||||
|
background-color: #072C24 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gold {
|
||||||
|
color: #E3CB9A;
|
||||||
|
text-decoration: none;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gold:hover {
|
||||||
|
color: #E3CB9A;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchInput {
|
||||||
|
min-width: 300px;
|
||||||
|
background-color: #1a322d;
|
||||||
|
color: #E3CB9A;
|
||||||
|
border: 1px solid #3d5a53;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchInput::placeholder {
|
||||||
|
color: #93B8B1;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
|
@ -5,28 +5,45 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Dark Athenaeum</title>
|
<title>Dark Athenaeum</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
<link href="/css/styles.css" rel="stylesheet">
|
<link href="/css/styles.css" rel="stylesheet">
|
||||||
<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=Lobster&family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Lobster&family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body class="dark-theme">
|
<body class="dark-theme">
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-black">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-black">
|
||||||
<div class="navbar-nav">
|
<div class="container">
|
||||||
<!-- Dla niezalogowanych -->
|
<a class="navbar-brand" href="/">DARK ATHENAEUM</a>
|
||||||
<div class="anonymous-links">
|
|
||||||
<a class="nav-link" href="/login.html">Logowanie</a>
|
|
||||||
<a class="nav-link" href="/register.html">Rejestracja</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Dla zalogowanych -->
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
<div class="user-links" style="display: none;">
|
<span class="navbar-toggler-icon"></span>
|
||||||
<a class="nav-link" href="/profile.html">Profil</a>
|
</button>
|
||||||
<a class="nav-link" href="#" id="logoutLink">Wyloguj</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a class="nav-link" href="/cart.html">
|
<div class="collapse navbar-collapse justify-content-end" id="navbarNav">
|
||||||
<i class="bi bi-basket"></i> Koszyk
|
<div class="navbar-nav gap-3">
|
||||||
</a>
|
<form class="d-flex" id="searchForm">
|
||||||
|
<input class="form-control me-2 dark-input"
|
||||||
|
type="search"
|
||||||
|
placeholder="Szukaj książek..."
|
||||||
|
aria-label="Search"
|
||||||
|
id="searchInput">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="anonymous-links d-flex gap-3">
|
||||||
|
<a class="nav-link" href="/login.html">Logowanie</a>
|
||||||
|
<a class="nav-link" href="/register.html">Rejestracja</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-links d-flex gap-3" style="display: none !important;">
|
||||||
|
<a class="nav-link" href="/profile.html">Profil</a>
|
||||||
|
<a class="nav-link" href="#" id="logoutLink">Wyloguj</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a class="nav-link" href="/cart.html">
|
||||||
|
<i class="bi bi-basket"></i> Koszyk
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
@ -43,6 +60,22 @@
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<footer class="mt-5 py-3 bg-dark">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center align-items-center text-center">
|
||||||
|
<div class="col-auto mb-2 mb-md-0">
|
||||||
|
<a href="https://sykorax.eu/" class="text-gold">O Nas</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<a href="https://sykorax.eu/" class="text-gold">Kontakt</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<a href="https://sykorax.eu/" class="text-gold">Współpraca</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
25
static/js/book.js
Normal file
25
static/js/book.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const bookId = urlParams.get('id');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/ksiazki/${bookId}`);
|
||||||
|
if (!response.ok) throw new Error('Książka nie znaleziona');
|
||||||
|
|
||||||
|
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 || 'Brak opisu';
|
||||||
|
document.getElementById('book-cover').src = book.obraz_url || 'https://via.placeholder.com/400x600';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Błąd:', error);
|
||||||
|
document.getElementById('book-details').innerHTML = `
|
||||||
|
<div class="col-12 text-center">
|
||||||
|
<h2 class="text-danger">Książka nie znaleziona</h2>
|
||||||
|
<a href="/" class="btn btn-gothic mt-3">Powrót do strony głównej</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,9 +1,19 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
updateNavVisibility();
|
||||||
|
setInterval(updateNavVisibility, 1000); // Aktualizuj co sekundę
|
||||||
|
});
|
||||||
|
|
||||||
function updateNavVisibility() {
|
function updateNavVisibility() {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
document.querySelectorAll('.anonymous-links, .user-links').forEach(el => {
|
document.querySelectorAll('.anonymous-links, .user-links').forEach(el => {
|
||||||
el.style.display = token ? 'none' : 'block';
|
el.style.display = 'none';
|
||||||
});
|
});
|
||||||
document.querySelector('.user-links').style.display = token ? 'block' : 'none';
|
|
||||||
|
if (token) {
|
||||||
|
document.querySelector('.user-links').style.display = 'flex';
|
||||||
|
} else {
|
||||||
|
document.querySelector('.anonymous-links').style.display = 'flex';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('logoutLink')?.addEventListener('click', (e) => {
|
document.getElementById('logoutLink')?.addEventListener('click', (e) => {
|
||||||
|
@ -82,54 +92,77 @@ document.getElementById('registerForm')?.addEventListener('submit', async (e) =>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadBooks() {
|
async function loadBooks(searchTerm = '') {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/ksiazki');
|
const response = await fetch(`/api/ksiazki?search=${encodeURIComponent(searchTerm)}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) throw new Error(`Błąd HTTP: ${response.status}`);
|
||||||
throw new Error('Network response was not ok');
|
|
||||||
}
|
|
||||||
const books = await response.json();
|
const books = await response.json();
|
||||||
const booksContainer = document.getElementById('books-container');
|
renderBooks(books);
|
||||||
booksContainer.innerHTML = '';
|
|
||||||
|
|
||||||
books.forEach(book => {
|
|
||||||
const bookElement = document.createElement('div');
|
|
||||||
bookElement.className = 'col-md-4';
|
|
||||||
bookElement.innerHTML = `
|
|
||||||
<div class="card mb-4">
|
|
||||||
<img src="${book.obraz_url || 'https://via.placeholder.com/150'}" class="card-img-top" alt="${book.tytul}">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">${book.tytul}</h5>
|
|
||||||
<p class="card-text">${book.autor}</p>
|
|
||||||
<p class="card-text">${book.cena} PLN</p>
|
|
||||||
<button class="btn btn-primary">Dodaj do koszyka</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
booksContainer.appendChild(bookElement);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading books:', error);
|
console.error('Błąd ładowania książek:', error);
|
||||||
const booksContainer = document.getElementById('books-container');
|
showError('Wystąpił błąd podczas ładowania książek');
|
||||||
booksContainer.innerHTML = '<div class="col-12 text-center"><p>Wystąpił błąd podczas ładowania książek.</p></div>';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inicjalizacja przy pierwszym załadowaniu
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
if (document.getElementById('books-container')) {
|
if (document.getElementById('books-container')) {
|
||||||
loadBooks();
|
loadBooks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nasłuchiwanie wyszukiwania
|
||||||
|
document.getElementById('searchInput')?.addEventListener('input', (e) => {
|
||||||
|
loadBooks(e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
updateNavVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.getElementById('searchForm')?.addEventListener('submit', async (e) => {
|
||||||
if (document.getElementById('books-container')) {
|
e.preventDefault();
|
||||||
loadBooks();
|
const searchTerm = document.querySelector('#searchForm input').value;
|
||||||
}
|
const response = await fetch(`/api/ksiazki?search=${encodeURIComponent(searchTerm)}`);
|
||||||
|
const books = await response.json();
|
||||||
|
renderBooks(books);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
function renderBooks(books) {
|
||||||
if (document.getElementById('books-container')) {
|
const container = document.getElementById('books-container');
|
||||||
loadBooks();
|
if (!container) return;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
container.innerHTML = books.map(book => `
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="book-card card h-100">
|
||||||
|
<a href="/book.html?id=${book.id}" class="book-link">
|
||||||
|
<div class="book-cover">
|
||||||
|
<img src="${book.obraz_url}"
|
||||||
|
class="card-img-top"
|
||||||
|
alt="${book.tytul}"
|
||||||
|
onerror="this.src='/images/placeholder.jpg'">
|
||||||
|
<div class="book-overlay">
|
||||||
|
<h5 class="book-title">${book.tytul}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(message) {
|
||||||
|
const container = document.getElementById('books-container');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="col-12 text-center py-5">
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
${message}<br>
|
||||||
|
<button class="btn btn-gothic mt-3" onclick="location.reload()">
|
||||||
|
Spróbuj ponownie
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', updateNavVisibility);
|
||||||
|
|
39
static/js/search.js
Normal file
39
static/js/search.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
document.getElementById('searchForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const searchTerm = document.getElementById('searchInput').value;
|
||||||
|
await loadResults(searchTerm);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadResults(searchTerm) {
|
||||||
|
const searchTerm = document.getElementById('searchInput').value;
|
||||||
|
const sortBy = document.getElementById('sortSelect').value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/ksiazki?search=${encodeURIComponent(searchTerm)}&sort=${sortBy}`);
|
||||||
|
const books = await response.json();
|
||||||
|
renderResults(books);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Błąd wyszukiwania:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
['input', 'change'].forEach(event => {
|
||||||
|
document.getElementById('searchInput').addEventListener(event, loadBooks);
|
||||||
|
document.getElementById('sortSelect').addEventListener(event, loadBooks);
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderResults(books) {
|
||||||
|
const container = document.getElementById('search-results');
|
||||||
|
container.innerHTML = books.map(book => `
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card dark-card h-100">
|
||||||
|
<img src="${book.obraz_url}" class="card-img-top" alt="${book.tytul}">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title lobster-font">${book.tytul}</h5>
|
||||||
|
<p class="card-text inter-font">${book.autor}</p>
|
||||||
|
<a href="/book.html?id=${book.id}" class="btn btn-gothic">Szczegóły</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
|
@ -4,28 +4,43 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Logowanie</title>
|
<title>Logowanie</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
<link href="/css/styles.css" rel="stylesheet">
|
<link href="/css/styles.css" rel="stylesheet">
|
||||||
<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=Lobster&family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Lobster&family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body class="dark-theme">
|
<body class="dark-theme">
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-black">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-black">
|
||||||
<div class="navbar-nav">
|
<div class="container">
|
||||||
<!-- Dla niezalogowanych -->
|
<a class="navbar-brand" href="/">DARK ATHENAEUM</a>
|
||||||
<div class="anonymous-links">
|
|
||||||
<a class="nav-link" href="/login.html">Logowanie</a>
|
|
||||||
<a class="nav-link" href="/register.html">Rejestracja</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Dla zalogowanych -->
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
<div class="user-links" style="display: none;">
|
<span class="navbar-toggler-icon"></span>
|
||||||
<a class="nav-link" href="/profile.html">Profil</a>
|
</button>
|
||||||
<a class="nav-link" href="#" id="logoutLink">Wyloguj</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a class="nav-link" href="/cart.html">
|
<div class="collapse navbar-collapse justify-content-end" id="navbarNav">
|
||||||
<i class="bi bi-basket"></i> Koszyk
|
<div class="navbar-nav gap-3">
|
||||||
</a>
|
<!-- Dodaj pole wyszukiwania -->
|
||||||
|
<form class="d-flex" id="searchForm">
|
||||||
|
<input class="form-control me-2 dark-input" type="search" placeholder="Szukaj książek..." aria-label="Search">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Linki -->
|
||||||
|
<div class="anonymous-links d-flex gap-3">
|
||||||
|
<a class="nav-link" href="/login.html">Logowanie</a>
|
||||||
|
<a class="nav-link" href="/register.html">Rejestracja</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-links d-flex gap-3" style="display: none;">
|
||||||
|
<a class="nav-link" href="/profile.html">Profil</a>
|
||||||
|
<a class="nav-link" href="#" id="logoutLink">Wyloguj</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a class="nav-link" href="/cart.html">
|
||||||
|
<i class="bi bi-basket"></i> Koszyk
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
@ -47,6 +62,22 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<footer class="mt-5 py-3 bg-dark">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center gap-3 gap-md-0">
|
||||||
|
<div class="col-auto">
|
||||||
|
<a href="https://sykorax.eu/" class="text-gold">O Nas</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<a href="https://sykorax.eu/" class="text-gold">Kontakt</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<a href="https://sykorax.eu/" class="text-gold">Współpraca</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -4,27 +4,42 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Rejestracja</title>
|
<title>Rejestracja</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
<link href="/css/styles.css" rel="stylesheet">
|
<link href="/css/styles.css" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Lobster&family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Lobster&family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body class="dark-theme">
|
<body class="dark-theme">
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-black">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-black">
|
||||||
<div class="navbar-nav">
|
<div class="container">
|
||||||
<!-- Dla niezalogowanych -->
|
<a class="navbar-brand" href="/">DARK ATHENAEUM</a>
|
||||||
<div class="anonymous-links">
|
|
||||||
<a class="nav-link" href="/login.html">Logowanie</a>
|
|
||||||
<a class="nav-link" href="/register.html">Rejestracja</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Dla zalogowanych -->
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
<div class="user-links" style="display: none;">
|
<span class="navbar-toggler-icon"></span>
|
||||||
<a class="nav-link" href="/profile.html">Profil</a>
|
</button>
|
||||||
<a class="nav-link" href="#" id="logoutLink">Wyloguj</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a class="nav-link" href="/cart.html">
|
<div class="collapse navbar-collapse justify-content-end" id="navbarNav">
|
||||||
<i class="bi bi-basket"></i> Koszyk
|
<div class="navbar-nav gap-3">
|
||||||
</a>
|
<!-- Dodaj pole wyszukiwania -->
|
||||||
|
<form class="d-flex" id="searchForm">
|
||||||
|
<input class="form-control me-2 dark-input" type="search" placeholder="Szukaj książek..." aria-label="Search">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Linki -->
|
||||||
|
<div class="anonymous-links d-flex gap-3">
|
||||||
|
<a class="nav-link" href="/login.html">Logowanie</a>
|
||||||
|
<a class="nav-link" href="/register.html">Rejestracja</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-links d-flex gap-3" style="display: none;">
|
||||||
|
<a class="nav-link" href="/profile.html">Profil</a>
|
||||||
|
<a class="nav-link" href="#" id="logoutLink">Wyloguj</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a class="nav-link" href="/cart.html">
|
||||||
|
<i class="bi bi-basket"></i> Koszyk
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
@ -51,6 +66,22 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<footer class="mt-5 py-3 bg-dark">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center gap-3 gap-md-0">
|
||||||
|
<div class="col-auto">
|
||||||
|
<a href="https://sykorax.eu/" class="text-gold">O Nas</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<a href="https://sykorax.eu/" class="text-gold">Kontakt</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<a href="https://sykorax.eu/" class="text-gold">Współpraca</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
16
static/search.html
Normal file
16
static/search.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input type="text" class="form-control dark-input" id="searchInput" placeholder="Szukaj...">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<select class="form-select dark-input" id="sortSelect">
|
||||||
|
<option value="default">Domyślnie</option>
|
||||||
|
<option value="price_asc">Cena rosnąco</option>
|
||||||
|
<option value="price_desc">Cena malejąco</option>
|
||||||
|
<option value="title_asc">Tytuł A-Z</option>
|
||||||
|
<option value="author_asc">Autor A-Z</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
Loading…
Add table
Reference in a new issue