Optymalizacja: UX i Bezpieczeństwo

PHP
Priorytet: Normalny Szkic

Zadanie 110.2: UX i Bezpieczeństwo

Wstęp

Masz działającą aplikację (Zadanie 110 i 110.1). Ale czy jest idealna? Co jeśli internet zwolni? Co jeśli ktoś doda produkt o nazwie <script>alert('Hacked')</script>? W tej części zajmiemy się User Experience (UX) oraz Bezpieczeństwem.

Cel zadania

Wdrożenie mechanizmu "Loadera" (kręciołka podczas ładowania), wyróżnianie kończących się produktów oraz ochrona przed atakami XSS (Cross-Site Scripting).

Wymagania techniczne

  1. Frontend (CSS/JS): Dodanie i obsługa loadera.
  2. Frontend (JS): Logika kolorowania wierszy (np. stock < 5).
  3. Frontend (JS): Funkcja sanityzująca dane tekstowe.

Kroki do wykonania

1. Loader (UX)

Użytkownik musi wiedzieć, że dane są pobierane. W index.html dodaj element loadera (np. nad tabelą), a w CSS go ostyluj.

/* style.css */
.loader {
    border: 5px solid #f3f3f3;
    border-top: 5px solid #3498db; 
    border-radius: 50%;
    width: 40px;
    height: 40px;
    animation: spin 1s linear infinite;
    /* Domyślnie ukryty czy widoczny? Zdecyduj. */
    display: none; 
    margin: 20px auto;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

W main.js obsłuż pokazywanie/ukrywanie loadera w funkcji fetchProducts:

/* main.js */
async function fetchProducts() {
    // 1. Pokaż loader przed startem pobierania:
    const loader = document.querySelector('.loader');
    loader.style.display = 'block';

    try {
        const response = await fetch('api/read.php');
        const data = await response.json();
        
        // 2. Ukryj loader po pobraniu danych:
        loader.style.display = 'none';

        renderTable(data);
    } catch (error) {
        // Pamiętaj o ukryciu loadera również w przypadku błędu!
        loader.style.display = 'none';
        console.error('Błąd:', error);
    }
}

[!IMPORTANT] Commit 1: Dodanie loadera CSS+JS.

2. Warunkowe Formatowanie

Wyróżnij produkty, których stan magazynowy jest niski (mniej niż 5 sztuk). Zmodyfikuj funkcję renderTable.

/* main.js */
// Wewnątrz pętli forEach:
const row = document.createElement('tr');

// Sprawdź warunek:
if (product.stock_quantity < 5) {
    // Dodaj klasę CSS lub zmień styl bezpośrednio:
    row.style.color = 'red';
    row.style.fontWeight = '...';
}

row.innerHTML = `...`;

[!IMPORTANT] Commit 2: Wyróżnianie niskiego stanu magazynowego.

3. Bezpieczeństwo XSS (Sanityzacja)

To krytyczny punkt! Jeśli nazwa produktu pochodzi od użytkownika, może zawierać złośliwy kod HTML/JS. Musisz go "uciec" (escape) przed wstawieniem do innerHTML.

Stwórz funkcję pomocniczą:

/* main.js */
function escapeHtml(text) {
    if (!text) return text;
    return text
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#039;");
}

Użyj jej w renderTable:

/* main.js */
// Zamiast: <td>${product.name}</td>
// Użyj:
<td>${escapeHtml(product.name)}</td>

[!CAUTION] Bez tej funkcji Twoja aplikacja jest podatna na ataki XSS! Spróbuj ręcznie (przez phpMyAdmin) zmienić nazwę produktu na <b>Gruby tekst</b>. Jeśli na stronie tekst nie będzie pogrubiony, ale zobaczysz znaczniki <b> - Twoja ochrona działa poprawnie.

[!IMPORTANT] Commit 3: Zabezpieczenie przed XSS.

4. Zadanie dla chętnych: Wyszukiwarka

Dodaj pole <input type="text" id="search"> nad tabelą. W JS nasłuchuj na zmiane (input event) i filtruj tablicę danych bez ponownego pytania API.

/* Podpowiedź */
const searchInput = document.getElementById('search');
let allProducts = []; // Tutaj przechowuj pobrane dane globalnie

// Po pobraniu danych:
allProducts = data;

searchInput.addEventListener('input', (e) => {
    const value = e.target.value.toLowerCase();
    
    // Użyj .filter() na tablicy allProducts
    const filtered = allProducts.filter(p => 
        p.name.toLowerCase().includes(value)
    );
    
    // Przerysuj tabelę z nowymi danymi
    renderTable(filtered);
});

[!IMPORTANT] Commit 4: Implementacja wyszukiwania (opcjonalne).