Optymalizacja: UX i Bezpieczeństwo
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
- Frontend (CSS/JS): Dodanie i obsługa loadera.
- Frontend (JS): Logika kolorowania wierszy (np.
stock < 5). - 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, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
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).