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",
|
||||
"env_logger",
|
||||
"log",
|
||||
"regex",
|
||||
"rust_decimal",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
@ -17,3 +17,4 @@ log = "0.4"
|
|||
bcrypt = "0.15"
|
||||
rust_decimal = { version = "1.37.1", features = ["serde", "db-postgres"] }
|
||||
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);
|
105
src/main.rs
105
src/main.rs
|
@ -11,6 +11,8 @@ use sqlx::postgres::PgPoolOptions;
|
|||
use rust_decimal::Decimal;
|
||||
use bigdecimal::BigDecimal;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::From;
|
||||
|
||||
#[derive(sqlx::FromRow, serde::Serialize)]
|
||||
struct Book {
|
||||
|
@ -18,26 +20,107 @@ struct Book {
|
|||
tytul: String,
|
||||
autor: String,
|
||||
cena: BigDecimal,
|
||||
obraz_url: Option<String>
|
||||
obraz_url: Option<String>,
|
||||
opis: Option<String>,
|
||||
}
|
||||
|
||||
#[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"#
|
||||
async fn get_ksiazki(
|
||||
pool: web::Data<sqlx::PgPool>,
|
||||
web::Query(params): web::Query<HashMap<String, String>>,
|
||||
) -> impl Responder {
|
||||
let search_term = params.get("search").map(|s| s.as_str()).unwrap_or("");
|
||||
let sort_by = params.get("sort").map(|s| s.as_str()).unwrap_or("default");
|
||||
|
||||
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
|
||||
)
|
||||
.fetch_all(pool.get_ref())
|
||||
.await
|
||||
{
|
||||
} 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),
|
||||
Err(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")]
|
||||
async fn check_auth() -> impl Responder {
|
||||
HttpResponse::Ok().json(json!({
|
||||
|
@ -81,6 +164,10 @@ async fn main() -> std::io::Result<()> {
|
|||
.service(get_ksiazki)
|
||||
.service(auth::rejestracja)
|
||||
.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"))
|
||||
})
|
||||
.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;
|
||||
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,21 +5,36 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<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-icons@1.11.0/font/bootstrap-icons.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.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="navbar-nav">
|
||||
<!-- Dla niezalogowanych -->
|
||||
<div class="anonymous-links">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
<!-- Dla zalogowanych -->
|
||||
<div class="user-links" style="display: none;">
|
||||
<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>
|
||||
|
@ -28,6 +43,8 @@
|
|||
<i class="bi bi-basket"></i> Koszyk
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-5">
|
||||
|
@ -43,6 +60,22 @@
|
|||
</div>
|
||||
</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>
|
||||
</body>
|
||||
</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() {
|
||||
const token = localStorage.getItem('token');
|
||||
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) => {
|
||||
|
@ -82,54 +92,77 @@ document.getElementById('registerForm')?.addEventListener('submit', async (e) =>
|
|||
}
|
||||
});
|
||||
|
||||
async function loadBooks() {
|
||||
async function loadBooks(searchTerm = '') {
|
||||
try {
|
||||
const response = await fetch('/api/ksiazki');
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
const response = await fetch(`/api/ksiazki?search=${encodeURIComponent(searchTerm)}`);
|
||||
if (!response.ok) throw new Error(`Błąd HTTP: ${response.status}`);
|
||||
const books = await response.json();
|
||||
const booksContainer = document.getElementById('books-container');
|
||||
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);
|
||||
});
|
||||
renderBooks(books);
|
||||
} catch (error) {
|
||||
console.error('Error loading books:', error);
|
||||
const booksContainer = document.getElementById('books-container');
|
||||
booksContainer.innerHTML = '<div class="col-12 text-center"><p>Wystąpił błąd podczas ładowania książek.</p></div>';
|
||||
console.error('Błąd ładowania książek:', error);
|
||||
showError('Wystąpił błąd podczas ładowania książek');
|
||||
}
|
||||
}
|
||||
|
||||
// Inicjalizacja przy pierwszym załadowaniu
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.getElementById('books-container')) {
|
||||
loadBooks();
|
||||
}
|
||||
|
||||
// Nasłuchiwanie wyszukiwania
|
||||
document.getElementById('searchInput')?.addEventListener('input', (e) => {
|
||||
loadBooks(e.target.value);
|
||||
});
|
||||
|
||||
updateNavVisibility();
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.getElementById('books-container')) {
|
||||
loadBooks();
|
||||
}
|
||||
document.getElementById('searchForm')?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
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', () => {
|
||||
if (document.getElementById('books-container')) {
|
||||
loadBooks();
|
||||
}
|
||||
});
|
||||
function renderBooks(books) {
|
||||
const container = document.getElementById('books-container');
|
||||
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,21 +4,34 @@
|
|||
<meta charset="UTF-8">
|
||||
<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-icons@1.11.0/font/bootstrap-icons.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.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="navbar-nav">
|
||||
<!-- Dla niezalogowanych -->
|
||||
<div class="anonymous-links">
|
||||
<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>
|
||||
|
||||
<!-- Dla zalogowanych -->
|
||||
<div class="user-links" style="display: none;">
|
||||
<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>
|
||||
|
@ -27,6 +40,8 @@
|
|||
<i class="bi bi-basket"></i> Koszyk
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="auth-container">
|
||||
|
@ -47,6 +62,22 @@
|
|||
</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>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -4,20 +4,33 @@
|
|||
<meta charset="UTF-8">
|
||||
<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-icons@1.11.0/font/bootstrap-icons.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="navbar-nav">
|
||||
<!-- Dla niezalogowanych -->
|
||||
<div class="anonymous-links">
|
||||
<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>
|
||||
|
||||
<!-- Dla zalogowanych -->
|
||||
<div class="user-links" style="display: none;">
|
||||
<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>
|
||||
|
@ -26,6 +39,8 @@
|
|||
<i class="bi bi-basket"></i> Koszyk
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="auth-container">
|
||||
|
@ -51,6 +66,22 @@
|
|||
</form>
|
||||
</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>
|
||||
</body>
|
||||
</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