From bdad6fdedd8a6aa5e9e1cab2d74026f6d158a5a6 Mon Sep 17 00:00:00 2001 From: Lheorvine Date: Sat, 24 May 2025 16:47:32 +0200 Subject: [PATCH] done homepage --- Cargo.lock | 1 + Cargo.toml | 1 + migrations/20250523170606_opis.sql | 1 + ...50523173412_add_opis_column_to_ksiazki.sql | 4 + migrations/bigdecimal.sql | 2 - src/main.rs | 107 +++++++++++++-- static/book.html | 83 +++++++++++ static/css/styles.css | 129 ++++++++++++++++++ static/index.html | 65 ++++++--- static/js/book.js | 25 ++++ static/js/main.js | 109 +++++++++------ static/js/search.js | 39 ++++++ static/login.html | 59 ++++++-- static/register.html | 59 ++++++-- static/search.html | 16 +++ 15 files changed, 606 insertions(+), 94 deletions(-) create mode 100644 migrations/20250523170606_opis.sql create mode 100644 migrations/20250523173412_add_opis_column_to_ksiazki.sql delete mode 100644 migrations/bigdecimal.sql create mode 100644 static/book.html create mode 100644 static/js/book.js create mode 100644 static/js/search.js create mode 100644 static/search.html diff --git a/Cargo.lock b/Cargo.lock index 3bff151..577de87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1414,6 +1414,7 @@ dependencies = [ "dotenv", "env_logger", "log", + "regex", "rust_decimal", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 4bcb168..d1b36d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/migrations/20250523170606_opis.sql b/migrations/20250523170606_opis.sql new file mode 100644 index 0000000..8ddc1d3 --- /dev/null +++ b/migrations/20250523170606_opis.sql @@ -0,0 +1 @@ +-- Add migration script here diff --git a/migrations/20250523173412_add_opis_column_to_ksiazki.sql b/migrations/20250523173412_add_opis_column_to_ksiazki.sql new file mode 100644 index 0000000..70577c7 --- /dev/null +++ b/migrations/20250523173412_add_opis_column_to_ksiazki.sql @@ -0,0 +1,4 @@ +ALTER TABLE ksiazki +ADD COLUMN opis TEXT; + +-- Add migration script here diff --git a/migrations/bigdecimal.sql b/migrations/bigdecimal.sql deleted file mode 100644 index a139067..0000000 --- a/migrations/bigdecimal.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE ksiazki -ALTER COLUMN cena TYPE NUMERIC(10,2); diff --git a/src/main.rs b/src/main.rs index 8cea107..11141f7 100644 --- a/src/main.rs +++ b/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 + obraz_url: Option, + opis: Option, } #[get("/api/ksiazki")] -async fn get_ksiazki(pool: web::Data) -> impl Responder { - match sqlx::query_as!( - Book, - r#"SELECT id, tytul, autor, cena, obraz_url FROM ksiazki"# - ) - .fetch_all(pool.get_ref()) - .await - { +async fn get_ksiazki( + pool: web::Data, + web::Query(params): web::Query>, +) -> 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 + ) + } 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) -> 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, + path: web::Path, +) -> 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")? diff --git a/static/book.html b/static/book.html new file mode 100644 index 0000000..dd02a63 --- /dev/null +++ b/static/book.html @@ -0,0 +1,83 @@ + + + + + Dark Athenaeum + + + + + + + +
+
+
+ Okładka książki +
+
+

+

+

+
+
+

Opis

+

+
+
+ +
+
+
+ + + + + + diff --git a/static/css/styles.css b/static/css/styles.css index 47110f2..1958863 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -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; +} diff --git a/static/index.html b/static/index.html index 603c914..436d7e6 100644 --- a/static/index.html +++ b/static/index.html @@ -5,28 +5,45 @@ Dark Athenaeum + @@ -43,6 +60,22 @@ + + diff --git a/static/js/book.js b/static/js/book.js new file mode 100644 index 0000000..e6cd2f1 --- /dev/null +++ b/static/js/book.js @@ -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 = ` +
+

Książka nie znaleziona

+ Powrót do strony głównej +
+ `; + } +}); diff --git a/static/js/main.js b/static/js/main.js index 84386a0..77fa015 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -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 = ` -
- ${book.tytul} -
-
${book.tytul}
-

${book.autor}

-

${book.cena} PLN

- -
-
- `; - booksContainer.appendChild(bookElement); - }); + renderBooks(books); } catch (error) { - console.error('Error loading books:', error); - const booksContainer = document.getElementById('books-container'); - booksContainer.innerHTML = '

Wystąpił błąd podczas ładowania książek.

'; + 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 => ` + + `).join(''); +} +function showError(message) { + const container = document.getElementById('books-container'); + if (!container) return; + + container.innerHTML = ` +
+
+ ${message}
+ +
+
+ `; +} + +document.addEventListener('DOMContentLoaded', updateNavVisibility); diff --git a/static/js/search.js b/static/js/search.js new file mode 100644 index 0000000..b7fabb9 --- /dev/null +++ b/static/js/search.js @@ -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 => ` +
+
+ ${book.tytul} +
+
${book.tytul}
+

${book.autor}

+ Szczegóły +
+
+
+ `).join(''); +} diff --git a/static/login.html b/static/login.html index aafeb8e..3e2344c 100644 --- a/static/login.html +++ b/static/login.html @@ -4,28 +4,43 @@ Logowanie + @@ -47,6 +62,22 @@ + + diff --git a/static/register.html b/static/register.html index 57653da..ece1e54 100644 --- a/static/register.html +++ b/static/register.html @@ -4,27 +4,42 @@ Rejestracja + @@ -51,6 +66,22 @@ + + diff --git a/static/search.html b/static/search.html new file mode 100644 index 0000000..97c93bd --- /dev/null +++ b/static/search.html @@ -0,0 +1,16 @@ +
+
+
+ +
+
+ +
+
+