Przejdź do głównej treści
INF.04-01-26.06-SGCzerwiec 2026 · 36 kroków · ok. 85 min · 32 kryteria z klucza

Rozwiązanie krok po kroku: Menedżer haseł i generator (Python + React)

Rozwiązujemy arkusz z menedżerem haseł w wersji Python + React: w konsoli funkcja, która losuje 12-znakowe hasło z pięciu zestawów znaków (w tym polskich liter) i wstawia każdy znak w losowe miejsce, a w przeglądarce jednokomponentowa aplikacja React z formularzem Bootstrapa, polem wyboru blokującym pole hasła i podsumowaniem zapisanych danych. Do tego dokumentacja funkcji według wzoru z arkusza, zrzuty i plik egzamin.

Kroki idą zdanie po zdaniu za treścią arkusza - każdy zaczyna się cytatem wymagania, a kod pokazuje dokładnie te linie, które je spełniają. Najpierw piszemy najprostszą wersję, której wymaga klucz, a w etapie „Dobre praktyki” przerabiamy ją na kod z prawdziwych projektów.

Co widać w podglądzie. Wyniki programu konsolowego pochodzą z prawdziwego uruchomienia kodu z danego kroku (Python 3.12) - hasła są naprawdę losowe. Podgląd aplikacji webowej wykonuje kod komponentu z kroku w prawdziwym Reakcie z Bootstrapem: możesz wpisywać dane, odznaczać pole wyboru i klikać „Zapisz”. Formularz budujemy według przykładu Bootstrapa z pliku szablonu w archiwum Web1.7z (form-group m-4, form-control, label z for, przycisk z marginesem m-2), przeniesionego do składni React.

Arkusz pozwala też na C#, C++ albo Javę w konsoli i na Angulara w części webowej - ten wariant to jedna z możliwych dróg.

Jak liczą się punkty. Klucz ma 32 kryteria (R.1.1 - R.4.9), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 24 punkty - można stracić 8 kryteriów i nadal zdać. Przy każdym kroku jest napisane, które kryteria spełnia; egzaminator ocenia spełnienie kryteriów z klucza, nie zgodność z tym kodem.

  1. Etap 1: Przygotowanie

    Krok 1 · po tym etapie masz 0 z 32 kryteriów (pkt)

    Krok 1: Folder zdającego i trzy podfoldery

    ok. 3 min
    Polecenie z arkusza

    Utwórz folder i nazwij go numerem zdającego. W folderze utwórz trzy podfoldery o nazwach: konsolowa, webowa, dokumentacja. Po wykonaniu zadania umieścisz w nich rezultaty swojej pracy.

    Na pulpicie zakładamy folder o nazwie z numerem PESEL, a w nim konsolowa, webowa i dokumentacja. Rozpakowujemy też archiwum Web1.7z z pulpitu (hasło podane w arkuszu: *LoGiny&H@sla8) - są w nim szablon.html z przykładowym formularzem Bootstrapa i znaki.txt z polskimi literami do skopiowania. Hasło archiwum zawiera *, & i @ - wpisujemy je ręcznie, uważnie, bo w oknie hasła nie widać znaków.

    Projekty możemy trzymać w domyślnych folderach środowisk - na koniec pakujemy je i kopiujemy do podfolderów.

  2. Etap 2: Aplikacja konsolowa

    Kroki 2-12 · po tym etapie masz 13 z 32 kryteriów (pkt)

    Krok 2: Nowy projekt konsola w PyCharm

    ok. 2 min
    Polecenie z arkusza

    Zastosowany obiektowy język programowania zgodny z zainstalowanym na stanowisku egzaminacyjnym: C++ lub C#, lub Java, lub Python. Dowolne podejście: strukturalne lub obiektowe.

    Wybieramy Pythona i podejście strukturalne (funkcje, bez klas) - arkusz pozwala na oba, a do jednej funkcji generującej hasło klasa nic nie wnosi. W PyCharm: New Project, nazwa konsola (stąd archiwum konsola.zip), interpreter zainstalowany na stanowisku, bez skryptu powitalnego. Tworzymy pusty plik main.py.

    konsola/main.py Python nowy plik
  3. Krok 3: Zestawy liter alfabetu łacińskiego

    Kryteria:

    R.1.4 Polskie lub angielskie nazewnictwo zmiennych/pól. Nazewnictwo jest znaczące. Wyjątkami od reguły są zmienne pomocnicze, np. bufor, tmp, iteratory pętli. Kryterium nie jest spełnione tylko wtedy, gdy nazwy zmiennych nic nie znaczą, np. x

    ok. 2 min
    Polecenie z arkusza

    Losowane są: 3 małe litery z alfabetu łacińskiego (26 znaków: a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z), 3 wielkie litery z alfabetu łacińskiego (26 znaków: A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z).

    Każdy zestaw to zwykły napis - z napisu wylosujemy jeden znak. Nazwy wielkimi literami (LOWERCASE, UPPERCASE) to w Pythonie umowny zapis stałych: wartości, których program nie zmienia. Na górze pliku importujemy moduł random - posłuży do losowania znaków i miejsc w haśle.

    Zestawy wypisujemy ręcznie. Python ma gotowe string.ascii_lowercase, ale napis z arkusza widać od razu w kodzie, a egzaminator nie musi sprawdzać, co kryje się w module.

    konsola/main.py Python zmienione: 3 linie
    import random
    
    LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
    UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  4. Krok 4: Zestaw liter ze znakami diakrytycznymi

    Kryteria:

    R.2.2 Zdefiniowane zestawy do losowania, przynajmniej dla znaków diakrytycznych ąęłńóśżźĄĘŁŃÓŚŻŹ oraz specjalnych !@#$%^&*()_+ wylosowane 2 znaki specjalne oraz 2 litery ze znakami diakrytycznymi z tych zestawów

    ok. 2 min
    Polecenie z arkusza

    2 litery ze znakami diakrytycznymi ze zbioru: ą, ę, ł, ń, ó, ś, ż, ź, Ą, Ę, Ł, Ń, Ó, Ś, Ż, Ź (patrz plik znaki.txt).

    Szesnaście polskich liter - małe i wielkie. Arkusz odsyła do pliku znaki.txt z archiwum: kopiujemy z niego znaki zamiast wpisywać je z klawiatury, bo przy szybkim pisaniu łatwo pomylić ż z ź. Python 3 przechowuje napisy w Unicode, więc polskie litery są zwykłymi znakami napisu; plik main.py zapisujemy w kodowaniu UTF-8 (domyślnym w PyCharm).

    konsola/main.py Python zmienione: 1 linia
    import random
    
    LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
    UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    DIACRITICS = "ąęłńóśżźĄĘŁŃÓŚŻŹ"
  5. Krok 5: Zestawy cyfr i znaków specjalnych

    Kryteria: ,

    R.2.2 Zdefiniowane zestawy do losowania, przynajmniej dla znaków diakrytycznych ąęłńóśżźĄĘŁŃÓŚŻŹ oraz specjalnych !@#$%^&*()_+ wylosowane 2 znaki specjalne oraz 2 litery ze znakami diakrytycznymi z tych zestawów

    R.1.5 Typy pasują do przypisanych im danych, np. hasło jest typu napisowego (w przypadku języka Python typ wynika z przypisanych danych)

    ok. 1 min
    Polecenie z arkusza

    2 cyfry z zakresu 0 – 9, 2 znaki specjalne z zestawu: !@#$%^&*()_+

    Cyfry też trzymamy jako napis "0123456789", nie jako liczby - hasło jest napisem, a wylosowana cyfra ma się do niego dokleić jak każdy inny znak. Znaki specjalne przepisujemy dokładnie w kolejności z arkusza; w cudzysłowie żaden z nich nie ma w Pythonie specjalnego znaczenia. Mamy pięć zestawów - komplet, o który pyta R.2.2.

    konsola/main.py Python zmienione: 2 linie
    import random
    
    LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
    UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    DIACRITICS = "ąęłńóśżźĄĘŁŃÓŚŻŹ"
    DIGITS = "0123456789"
    SPECIAL = "!@#$%^&*()_+"
  6. Krok 6: Funkcja wstawiająca znak w losowe miejsce

    Kryteria: ,

    R.2.4 Każdy z 12 znaków jest umieszczony w losowym miejscu w haśle

    R.1.3 Znaczące polskie lub angielskie nazewnictwo metody/funkcji

    ok. 3 min
    Polecenie z arkusza

    Każdy z wylosowanych 12 znaków jest umieszczany w losowym miejscu w haśle.

    Pomocnicza funkcja robi dwa losowania. random.choice(characters) wybiera jeden znak z zestawu. random.randint(0, len(password)) losuje miejsce - od 0 (przed pierwszym znakiem) do długości hasła (za ostatnim), obie granice włącznie. Wycinki password[:position] i password[position:] dzielą napis w tym miejscu, a znak trafia pomiędzy. Funkcja zwraca nowy napis, bo napisów w Pythonie nie da się zmienić w miejscu.

    Gdy każdy znak trafia w losowe miejsce, kolejność grup (małe, wielkie, polskie...) znika - dokładnie o to chodzi w R.2.4. Arkusz zabrania gotowej funkcji generującej całe hasło; losowanie pojedynczego znaku czy liczby to dozwolone cegiełki.

    Typowe błędy

    random.randint(0, len(password) - 1) nigdy nie wstawi znaku na KOŃCU hasła - ostatnie miejsce byłoby nieosiągalne i rozkład przestałby być równy.

    konsola/main.py Python zmienione: 6 linii
    import random
    
    LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
    UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    DIACRITICS = "ąęłńóśżźĄĘŁŃÓŚŻŹ"
    DIGITS = "0123456789"
    SPECIAL = "!@#$%^&*()_+"
    
    
    def insert_random_char(password, characters):
        char = random.choice(characters)
        position = random.randint(0, len(password))
        return password[:position] + char + password[position:]
  7. Krok 7: Funkcja generate_password i zmienna hasła

    Kryteria: , ,

    R.2.1 Zdefiniowana zmienna typu napisowego dla hasła oraz funkcja zwraca napis (wnętrze funkcji może być z błędami)

    R.1.3 Znaczące polskie lub angielskie nazewnictwo metody/funkcji

    R.1.5 Typy pasują do przypisanych im danych, np. hasło jest typu napisowego (w przypadku języka Python typ wynika z przypisanych danych)

    ok. 1 min
    Polecenie z arkusza

    Hasło przechowywane w zmiennej / polu typu napisowego. Funkcja / metoda zwracająca wygenerowane 12-znakowe hasło według zasad.

    Szkielet głównej funkcji: zmienna password zaczyna jako pusty napis "" (typ napisowy wynika w Pythonie z przypisanej wartości - R.1.5) i jest zwracana przez return. Kolejne kroki będą dokładać do niej znaki. Nazwa mówi, co funkcja robi (R.1.3).

    konsola/main.py Python zmienione: 5 linii
    import random
    
    LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
    UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    DIACRITICS = "ąęłńóśżźĄĘŁŃÓŚŻŹ"
    DIGITS = "0123456789"
    SPECIAL = "!@#$%^&*()_+"
    
    
    def insert_random_char(password, characters):
        char = random.choice(characters)
        position = random.randint(0, len(password))
        return password[:position] + char + password[position:]
    
    
    def generate_password():
        password = ""
        return password
  8. Krok 8: Trzy małe i trzy wielkie litery

    Kryteria: ,

    R.2.3 Wylosowane 3 małe litery, 3 wielkie litery, 2 cyfry

    R.1.2 Kod zapisany z wcięciami dla zagnieżdżeń bloków

    ok. 2 min
    Polecenie z arkusza

    Losowane są: 3 małe litery z alfabetu łacińskiego, 3 wielkie litery z alfabetu łacińskiego.

    for _ in range(3) wykonuje blok trzy razy; podkreślnik _ to umowna nazwa zmiennej, której nie używamy (klucz dopuszcza takie nazwy pomocnicze - R.1.4). Każde przejście wynik funkcji pomocniczej zapisuje z powrotem do password - bez przypisania wylosowany znak by przepadł. Wcięcia (4 spacje) wyznaczają w Pythonie bloki, więc są częścią składni (R.1.2).

    konsola/main.py Python zmienione: 4 linie
    import random
    
    LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
    UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    DIACRITICS = "ąęłńóśżźĄĘŁŃÓŚŻŹ"
    DIGITS = "0123456789"
    SPECIAL = "!@#$%^&*()_+"
    
    
    def insert_random_char(password, characters):
        char = random.choice(characters)
        position = random.randint(0, len(password))
        return password[:position] + char + password[position:]
    
    
    def generate_password():
        password = ""
        for _ in range(3):
            password = insert_random_char(password, LOWERCASE)
        for _ in range(3):
            password = insert_random_char(password, UPPERCASE)
        return password
  9. Krok 9: Dwie litery ze znakami diakrytycznymi

    Kryteria:

    R.2.2 Zdefiniowane zestawy do losowania, przynajmniej dla znaków diakrytycznych ąęłńóśżźĄĘŁŃÓŚŻŹ oraz specjalnych !@#$%^&*()_+ wylosowane 2 znaki specjalne oraz 2 litery ze znakami diakrytycznymi z tych zestawów

    ok. 1 min
    Polecenie z arkusza

    2 litery ze znakami diakrytycznymi ze zbioru: ą, ę, ł, ń, ó, ś, ż, ź, Ą, Ę, Ł, Ń, Ó, Ś, Ż, Ź (patrz plik znaki.txt).

    Ta sama pętla, inny zestaw i liczba powtórzeń 2. Dzięki funkcji pomocniczej każda grupa to dwie linie - nie powtarzamy kodu losowania pięć razy.

    konsola/main.py Python zmienione: 2 linie
    import random
    
    LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
    UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    DIACRITICS = "ąęłńóśżźĄĘŁŃÓŚŻŹ"
    DIGITS = "0123456789"
    SPECIAL = "!@#$%^&*()_+"
    
    
    def insert_random_char(password, characters):
        char = random.choice(characters)
        position = random.randint(0, len(password))
        return password[:position] + char + password[position:]
    
    
    def generate_password():
        password = ""
        for _ in range(3):
            password = insert_random_char(password, LOWERCASE)
        for _ in range(3):
            password = insert_random_char(password, UPPERCASE)
        for _ in range(2):
            password = insert_random_char(password, DIACRITICS)
        return password
  10. Krok 10: Dwie cyfry i dwa znaki specjalne

    Kryteria: , ,

    R.2.2 Zdefiniowane zestawy do losowania, przynajmniej dla znaków diakrytycznych ąęłńóśżźĄĘŁŃÓŚŻŹ oraz specjalnych !@#$%^&*()_+ wylosowane 2 znaki specjalne oraz 2 litery ze znakami diakrytycznymi z tych zestawów

    R.2.3 Wylosowane 3 małe litery, 3 wielkie litery, 2 cyfry

    R.2.5 Funkcja zwraca wygenerowane hasło

    ok. 2 min
    Polecenie z arkusza

    2 cyfry z zakresu 0 – 9, 2 znaki specjalne z zestawu: !@#$%^&*()_+. Hasło jest zapisane w zmiennej napisowej i zwracane z funkcji.

    Ostatnie dwie grupy. Razem: 3 + 3 + 2 + 2 + 2 = 12 znaków, każdy wstawiony w losowe miejsce. Funkcja jest kompletna i zwraca gotowe hasło (R.2.5).

    konsola/main.py Python zmienione: 4 linie
    import random
    
    LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
    UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    DIACRITICS = "ąęłńóśżźĄĘŁŃÓŚŻŹ"
    DIGITS = "0123456789"
    SPECIAL = "!@#$%^&*()_+"
    
    
    def insert_random_char(password, characters):
        char = random.choice(characters)
        position = random.randint(0, len(password))
        return password[:position] + char + password[position:]
    
    
    def generate_password():
        password = ""
        for _ in range(3):
            password = insert_random_char(password, LOWERCASE)
        for _ in range(3):
            password = insert_random_char(password, UPPERCASE)
        for _ in range(2):
            password = insert_random_char(password, DIACRITICS)
        for _ in range(2):
            password = insert_random_char(password, DIGITS)
        for _ in range(2):
            password = insert_random_char(password, SPECIAL)
        return password
  11. Krok 11: Program główny: pięć haseł

    Kryteria: , ,

    R.2.7 Program wyświetla pięć wygenerowanych haseł (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    R.1.7 Komunikacja z użytkownikiem prowadzona jest w sposób zrozumiały. Jeżeli kod nie uruchamia się z powodu błędów kompilacji - sprawdzić w kodzie aplikacji

    R.1.1 Kod źródłowy zapisany w sposób czytelny: instrukcje w osobnych liniach, stosowane spacje pomiędzy operatorami, konsekwentnie stosowana wybrana konwencja dla nawiasów klamrowych instrukcji blokowej

    ok. 2 min
    Polecenie z arkusza

    Aplikacja w programie głównym generuje 5 haseł i wyświetla je ze znaczącym komunikatem. Komunikacja z użytkownikiem jest prowadzona w sposób zrozumiały.

    Kod poza funkcjami wykonuje się po uruchomieniu pliku - to nasz program główny. Najpierw komunikat, co program robi, potem pętla range(1, 6) (liczby od 1 do 5 - górna granica nie wchodzi). f-napis f"Hasło {number}: ..." wstawia w miejsca w klamrach numer i wynik funkcji. Uruchamiamy: prawy przycisk na main.py → Run 'main' - wynik obok, każde hasło ma 12 znaków i inny układ.

    Typowe błędy

    Pętla range(5) z wypisywaniem number pokaże numery od 0 do 4 - program działa, ale „Hasło 0” to komunikat, który użytkownika myli.

    konsola/main.py Python zmienione: 5 linii
    import random
    
    LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
    UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    DIACRITICS = "ąęłńóśżźĄĘŁŃÓŚŻŹ"
    DIGITS = "0123456789"
    SPECIAL = "!@#$%^&*()_+"
    
    
    def insert_random_char(password, characters):
        char = random.choice(characters)
        position = random.randint(0, len(password))
        return password[:position] + char + password[position:]
    
    
    def generate_password():
        password = ""
        for _ in range(3):
            password = insert_random_char(password, LOWERCASE)
        for _ in range(3):
            password = insert_random_char(password, UPPERCASE)
        for _ in range(2):
            password = insert_random_char(password, DIACRITICS)
        for _ in range(2):
            password = insert_random_char(password, DIGITS)
        for _ in range(2):
            password = insert_random_char(password, SPECIAL)
        return password
    
    
    print("Generator bezpiecznych haseł - pięć haseł po 12 znaków:")
    for number in range(1, 6):
        print(f"Hasło {number}: {generate_password()}")
    Wynik programu po tym kroku
    > python main.py
    Generator bezpiecznych haseł - pięć haseł po 12 znaków:
    Hasło 1: cWv(4A$J4łów
    Hasło 2: I9ftA@ÓąiR6$
    Hasło 3: L!yp5)V1ŁgęR
    Hasło 4: Jo)ńvA&4ęiS2
    Hasło 5: 7+Ót1DGKźoc%
  12. Krok 12: Folder konsolowa: archiwum i źródło

    Kryteria:

    R.4.9 Przygotowana jest dokumentacja w postaci płyty: oba projekty są spakowane oraz zmodyfikowane pliki są umieszczone na zewnątrz archiwów

    ok. 2 min
    Polecenie z arkusza

    Kod aplikacji przygotuj do nagrania na płytę. W folderze konsolowa należy umieścić archiwum całego projektu o nazwie konsola.zip, i skopiowany z projektu plik z kodem źródłowym programu (modyfikowane pliki) oraz plik wykonywalny, jeżeli istnieje.

    Folder projektu konsola pakujemy do konsola.zip, obok kopiujemy main.py. Python jest interpretowany, więc pliku wykonywalnego nie ma - nie trzeba go tworzyć. (Zrzuty ekranu w tym arkuszu idą do folderu dokumentacja - wrócimy do nich w części III.) Archiwum robimy na samym końcu pracy, po dopisaniu komentarza z części III - inaczej w konsola.zip zostałaby wersja bez dokumentacji.

    Wynik programu bez zmian - taki jak po kroku 11.

  13. Etap 3: Aplikacja webowa

    Kroki 13-28 · po tym etapie masz 21 z 32 kryteriów (pkt)

    Krok 13: Nowy projekt React w Vite

    ok. 4 min
    Polecenie z arkusza

    Z zastosowaniem dostępnego na stanowisku egzaminacyjnym frameworka Angular lub biblioteki React.js wykonaj aplikację internetową typu front-end obsługującą logowanie.

    Wybieramy React. Projekt zakładamy w terminalu (w PyCharm albo VS Code: Terminal) narzędziem Vite:

    npm create vite@latest web -- --template react
    cd web
    npm install
    

    Nazwa web od razu pasuje do archiwum web.zip, którego wymaga arkusz. Vite tworzy m.in. src/main.jsx - punkt startowy, który wstawia komponent App do elementu #root strony - i src/App.jsx z przykładowym licznikiem, który za chwilę zastąpimy. npm run dev uruchamia serwer pod http://localhost:5173.

    Jeśli na stanowisku jest tylko create-react-app (npx create-react-app web), kroki są takie same - komponent leży w src/App.js.

    web/src/main.jsx JSX nowy plik
    import { StrictMode } from 'react'
    import { createRoot } from 'react-dom/client'
    import './index.css'
    import App from './App.jsx'
    
    createRoot(document.getElementById('root')).render(
      <StrictMode>
        <App />
      </StrictMode>,
    )

    Wynik programu bez zmian - taki jak po kroku 11.

  14. Krok 14: Biblioteka Bootstrap w projekcie

    Kryteria:

    R.3.2 Do projektu dołączona jest biblioteka Bootstrap oraz sformatowane: przycisk klasą co najmniej: btn btn-primary pierwszy paragraf klasą co najmniej: lead text-primary

    ok. 2 min
    Polecenie z arkusza

    Do utworzenia formularza ostylowanego biblioteką Bootstrap należy posłużyć się przykładem z pliku szablon.html wypakowanego z archiwum. Fragmenty kodu HTML należy zmodyfikować zgodnie ze składnią Angular lub React.js.

    Bootstrap instalujemy jako pakiet: npm install bootstrap. Arkusz (tabela 1) podaje, jak go dołączyć w React: import 'bootstrap/dist/css/bootstrap.css'. Wstawiamy ten import do main.jsx w miejsce import './index.css' - arkusz stylów z szablonu Vite ustawia ciemne tło i wyśrodkowuje całą stronę, co gryzłoby się z układem z ilustracji 1. Import w main.jsx działa na całą aplikację.

    Typowe błędy

    npm install bootstrap uruchomione poza folderem web instaluje pakiet gdzie indziej - import zgłosi wtedy błąd „Failed to resolve import”. Polecenia npm wykonujemy w folderze projektu.

    web/src/main.jsx JSX zmienione: 1 linia
    import { StrictMode } from 'react'
    import { createRoot } from 'react-dom/client'
    import 'bootstrap/dist/css/bootstrap.css'
    import App from './App.jsx'
    
    createRoot(document.getElementById('root')).render(
      <StrictMode>
        <App />
      </StrictMode>,
    )

    Wynik programu bez zmian - taki jak po kroku 11.

  15. Krok 15: Komponent App z dwoma blokami

    Kryteria:

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    ok. 2 min
    Polecenie z arkusza

    Aplikacja składa się z jednego komponentu, w którym znajdują się formularz i podsumowanie danych z formularza. Komponent składa się z dwóch bloków.

    Zastępujemy całą zawartość App.jsx. Komponent w React to funkcja, która zwraca JSX - zapis podobny do HTML wewnątrz JavaScriptu. Zwracamy jeden element: div z klasą Bootstrapa container (wyśrodkowana kolumna o rozsądnej szerokości), a w nim dwa bloki - pierwszy na nagłówek i formularz, drugi na podsumowanie. W JSX atrybut class piszemy jako className - tabela 1 arkusza mówi to wprost, bo class jest w JavaScripcie słowem zarezerwowanym.

    Plik App.css i obrazki z szablonu nie są już używane - można je usunąć. Podgląd jest jeszcze pusty.

    web/src/App.jsx JSX nowy plik
    function App() {
      return (
        <div className="container">
          <div>
          </div>
          <div>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  16. Krok 16: Nagłówek pierwszego stopnia

    Kryteria:

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    ok. 1 min
    Polecenie z arkusza

    Elementy bloku pierwszego: Nagłówek pierwszego stopnia o treści „Menedżer haseł - wykonał”, dalej wstawiony numer zdającego.

    <h1> z treścią z arkusza. Na egzaminie po słowie „wykonał” wpisujemy numer PESEL - w tym rozwiązaniu stoi tam autor, Bartosz Bryniarski. Rozmiar i krój nagłówka nadaje już Bootstrap.

    web/src/App.jsx JSX zmienione: 1 linia
    function App() {
      return (
        <div className="container">
          <div>
            <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
          </div>
          <div>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  17. Krok 17: Formularz i pole Adres WWW

    Kryteria: ,

    R.3.3 Pola edycyjne są powiązane z etykietami za pomocą atrybutu for oraz zastosowane formatowanie Bootstrap do kontrolek np.: <div class="form-group m-4"> <label for="adres">Adres WWW:</label> <input id="adres" class="form-control" .../> </div> W React.js zastosowane className zamiast class oraz domknięte wszystkie znaczniki, poprawne użycie htmlFor.

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    ok. 3 min
    Polecenie z arkusza

    Pola edycyjne są powiązane z etykietami: „Adres WWW:”, „Login:”, „Hasło:”. Pola do wprowadzenia adresu WWW i loginu zawierają tekst podpowiedzi: "Adres strony WWW", "Twój login".

    Każde pole przenosimy z szablon.html: div z klasą form-group (grupa etykieta + pole) i m-4 (margines 1,5 rem, jak odsunięcie pól na ilustracji 1), w nim label i input z klasą form-control (pełna szerokość, ramka, zaokrąglenie). Etykietę wiążemy z polem: htmlFor="url" w etykiecie wskazuje id="url" pola - kliknięcie etykiety ustawia kursor w polu. W JSX for zapisujemy jako htmlFor (z tego samego powodu co className), a znacznik input musi być domknięty ukośnikiem />. Podpowiedź to atrybut placeholder.

    Typowe błędy

    <label for="url"> albo <input ...> bez /> - w React pierwsze daje ostrzeżenie i nie wiąże etykiety, drugie jest błędem składni JSX, a klucz (R.3.3) wymaga htmlFor i domkniętych znaczników.

    web/src/App.jsx JSX zmienione: 6 linii
    function App() {
      return (
        <div className="container">
          <div>
            <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
            <form>
              <div className="form-group m-4">
                <label htmlFor="url">Adres WWW:</label>
                <input id="url" type="text" className="form-control" placeholder="Adres strony WWW" />
              </div>
            </form>
          </div>
          <div>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  18. Krok 18: Pole Login

    Kryteria:

    R.3.3 Pola edycyjne są powiązane z etykietami za pomocą atrybutu for oraz zastosowane formatowanie Bootstrap do kontrolek np.: <div class="form-group m-4"> <label for="adres">Adres WWW:</label> <input id="adres" class="form-control" .../> </div> W React.js zastosowane className zamiast class oraz domknięte wszystkie znaczniki, poprawne użycie htmlFor.

    ok. 1 min
    Polecenie z arkusza

    Pola edycyjne są powiązane z etykietami: „Adres WWW:”, „Login:”, „Hasło:”. Pola do wprowadzenia adresu WWW i loginu zawierają tekst podpowiedzi: "Adres strony WWW", "Twój login".

    Drugie pole według tego samego wzoru, z identyfikatorem login i podpowiedzią „Twój login”. Identyfikatory muszą być różne - inaczej obie etykiety wskazywałyby pierwsze pole.

    web/src/App.jsx JSX zmienione: 4 linie
    function App() {
      return (
        <div className="container">
          <div>
            <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
            <form>
              <div className="form-group m-4">
                <label htmlFor="url">Adres WWW:</label>
                <input id="url" type="text" className="form-control" placeholder="Adres strony WWW" />
              </div>
              <div className="form-group m-4">
                <label htmlFor="login">Login:</label>
                <input id="login" type="text" className="form-control" placeholder="Twój login" />
              </div>
            </form>
          </div>
          <div>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  19. Krok 19: Pole Hasło typu password

    Kryteria: ,

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    R.3.3 Pola edycyjne są powiązane z etykietami za pomocą atrybutu for oraz zastosowane formatowanie Bootstrap do kontrolek np.: <div class="form-group m-4"> <label for="adres">Adres WWW:</label> <input id="adres" class="form-control" .../> </div> W React.js zastosowane className zamiast class oraz domknięte wszystkie znaczniki, poprawne użycie htmlFor.

    ok. 1 min
    Polecenie z arkusza

    Pole do wpisania hasła umożliwia ukrywanie wpisywanych znaków.

    Trzecie pole ma type="password" - przeglądarka zamienia wpisywane znaki na kropki. To jedyna różnica wobec pól tekstowych; podpowiedzi arkusz tu nie przewiduje.

    web/src/App.jsx JSX zmienione: 4 linie
    function App() {
      return (
        <div className="container">
          <div>
            <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
            <form>
              <div className="form-group m-4">
                <label htmlFor="url">Adres WWW:</label>
                <input id="url" type="text" className="form-control" placeholder="Adres strony WWW" />
              </div>
              <div className="form-group m-4">
                <label htmlFor="login">Login:</label>
                <input id="login" type="text" className="form-control" placeholder="Twój login" />
              </div>
              <div className="form-group m-4">
                <label htmlFor="password">Hasło:</label>
                <input id="password" type="password" className="form-control" />
              </div>
            </form>
          </div>
          <div>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  20. Krok 20: Pole wyboru Wygeneruj hasło, domyślnie zaznaczone

    Kryteria: ,

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    R.3.7 W stanie początkowym checkbox jest zaznaczony, pole do wpisywania hasła jest nieaktywne, gdy checkbox zostanie odznaczony - pole hasła jest aktywne, przy ponownym zaznaczeniu - staje się nieaktywne (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    ok. 3 min
    Polecenie z arkusza

    Pole wyboru jest domyślnie zaznaczone i pole do wpisania hasła jest nieaktywne.

    Stan pola wyboru trzymamy w stanie komponentu: useState(true) zwraca parę - bieżącą wartość generate (na start true, czyli zaznaczone) i funkcję setGenerate do jej zmiany. useState importujemy z react na górze pliku.

    Pole wyboru jest kontrolowane: checked={generate} pokazuje stan, a onChange po każdym kliknięciu zapisuje nową wartość event.target.checked. W JSX wartości JavaScriptu wstawiamy w klamrach {...}. Klasy form-check, form-check-input i form-check-label to wzór Bootstrapa dla pól wyboru; pole stoi nad hasłem, jak na ilustracji 1, a grupa hasła dostała marginesy tylko z boków i u dołu (mx-4 mb-4), żeby przylegała do pola wyboru.

    Kliknij pole wyboru w podglądzie - przełącza się, ale pole hasła jeszcze na to nie reaguje.

    Typowe błędy

    Event onClick={setGenerate(false)} (wywołanie zamiast funkcji) wykona się już przy rysowaniu komponentu i zapętli aplikację. Do zdarzenia przekazujemy funkcję: onChange={(event) => ...}.

    web/src/App.jsx JSX zmienione: 10 linii
    import { useState } from 'react'
    
    function App() {
      const [generate, setGenerate] = useState(true)
    
      return (
        <div className="container">
          <div>
            <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
            <form>
              <div className="form-group m-4">
                <label htmlFor="url">Adres WWW:</label>
                <input id="url" type="text" className="form-control" placeholder="Adres strony WWW" />
              </div>
              <div className="form-group m-4">
                <label htmlFor="login">Login:</label>
                <input id="login" type="text" className="form-control" placeholder="Twój login" />
              </div>
              <div className="form-check mx-4">
                <input id="generate" type="checkbox" className="form-check-input"
                  checked={generate} onChange={(event) => setGenerate(event.target.checked)} />
                <label htmlFor="generate" className="form-check-label">Wygeneruj hasło</label>
              </div>
              <div className="form-group mx-4 mb-4">
                <label htmlFor="password">Hasło:</label>
                <input id="password" type="password" className="form-control" />
              </div>
            </form>
          </div>
          <div>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  21. Krok 21: Pole hasła nieaktywne, gdy pole wyboru jest zaznaczone

    Kryteria: ,

    R.3.4 Zapisana przynajmniej w jednym miejscu realizacja powiązania stanu kontrolki checkbox z polem hasła

    R.3.7 W stanie początkowym checkbox jest zaznaczony, pole do wpisywania hasła jest nieaktywne, gdy checkbox zostanie odznaczony - pole hasła jest aktywne, przy ponownym zaznaczeniu - staje się nieaktywne (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    ok. 2 min
    Polecenie z arkusza

    Gdy pole wyboru jest zaznaczone to pole do wpisania hasła jest nieaktywne (ilustracja 2), w przeciwnym wypadku jest aktywne (ilustracja 3).

    Jedno powiązanie: disabled={generate}. Gdy generate ma wartość true, pole jest nieaktywne (szare, nie da się w nim pisać); po odznaczeniu React rysuje komponent ponownie z false i pole się odblokowuje. Nie piszemy żadnego if ani getElementById - widok wynika ze stanu. Odznacz i zaznacz pole wyboru w podglądzie: pole hasła przełącza się razem z nim (R.3.4, R.3.7).

    Typowe błędy

    document.getElementById("password").disabled = ... - klucz wprost odbiera za metody DOM CAŁY rezultat 3 (wszystkie kryteria aplikacji webowej). W React zmieniamy stan, nie elementy strony.

    web/src/App.jsx JSX zmienione: 1 linia
    import { useState } from 'react'
    
    function App() {
      const [generate, setGenerate] = useState(true)
    
      return (
        <div className="container">
          <div>
            <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
            <form>
              <div className="form-group m-4">
                <label htmlFor="url">Adres WWW:</label>
                <input id="url" type="text" className="form-control" placeholder="Adres strony WWW" />
              </div>
              <div className="form-group m-4">
                <label htmlFor="login">Login:</label>
                <input id="login" type="text" className="form-control" placeholder="Twój login" />
              </div>
              <div className="form-check mx-4">
                <input id="generate" type="checkbox" className="form-check-input"
                  checked={generate} onChange={(event) => setGenerate(event.target.checked)} />
                <label htmlFor="generate" className="form-check-label">Wygeneruj hasło</label>
              </div>
              <div className="form-group mx-4 mb-4">
                <label htmlFor="password">Hasło:</label>
                <input id="password" type="password" className="form-control" disabled={generate} />
              </div>
            </form>
          </div>
          <div>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  22. Krok 22: Niebieski przycisk Zapisz

    Kryteria: ,

    R.3.2 Do projektu dołączona jest biblioteka Bootstrap oraz sformatowane: przycisk klasą co najmniej: btn btn-primary pierwszy paragraf klasą co najmniej: lead text-primary

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    ok. 1 min
    Polecenie z arkusza

    Przycisk „Zapisz” jest koloru niebieskiego za sprawą odpowiedniej klasy Bootstrap.

    Klasy btn btn-primary: btn to wygląd przycisku Bootstrapa, btn-primary - kolor podstawowy, niebieski (tabela 1 arkusza). W szablonie w miejscu koloru są trzy kropki (btn ... m-2) - to luka do uzupełnienia; margines m-2 (0,5 rem) zostawiamy z szablonu. type="submit" sprawia, że kliknięcie zatwierdza formularz - to zdarzenie obsłużymy za chwilę. Blok pierwszy jest kompletny i odpowiada ilustracji 1.

    web/src/App.jsx JSX zmienione: 1 linia
    import { useState } from 'react'
    
    function App() {
      const [generate, setGenerate] = useState(true)
    
      return (
        <div className="container">
          <div>
            <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
            <form>
              <div className="form-group m-4">
                <label htmlFor="url">Adres WWW:</label>
                <input id="url" type="text" className="form-control" placeholder="Adres strony WWW" />
              </div>
              <div className="form-group m-4">
                <label htmlFor="login">Login:</label>
                <input id="login" type="text" className="form-control" placeholder="Twój login" />
              </div>
              <div className="form-check mx-4">
                <input id="generate" type="checkbox" className="form-check-input"
                  checked={generate} onChange={(event) => setGenerate(event.target.checked)} />
                <label htmlFor="generate" className="form-check-label">Wygeneruj hasło</label>
              </div>
              <div className="form-group mx-4 mb-4">
                <label htmlFor="password">Hasło:</label>
                <input id="password" type="password" className="form-control" disabled={generate} />
              </div>
              <button type="submit" className="btn btn-primary m-2">Zapisz</button>
            </form>
          </div>
          <div>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  23. Krok 23: Stan pól tekstowych

    Kryteria:

    R.1.4 Polskie lub angielskie nazewnictwo zmiennych/pól. Nazewnictwo jest znaczące. Wyjątkami od reguły są zmienne pomocnicze, np. bufor, tmp, iteratory pętli. Kryterium nie jest spełnione tylko wtedy, gdy nazwy zmiennych nic nie znaczą, np. x

    ok. 3 min
    Polecenie z arkusza

    Elementy bloku drugiego: Elementy są wypełniane po zatwierdzeniu formularza przyciskiem.

    Żeby po zatwierdzeniu wypisać dane, komponent musi je znać. Każde pole tekstowe dostaje własny stan (url, login, password, na start puste napisy) i staje się polem kontrolowanym: value pokazuje stan, a onChange zapisuje każdy wpisany znak przez event.target.value. Na ekranie nic się nie zmienia - pola działają jak wcześniej, tylko React pamięta ich treść.

    web/src/App.jsx JSX zmienione: 9 linii
    import { useState } from 'react'
    
    function App() {
      const [url, setUrl] = useState('')
      const [login, setLogin] = useState('')
      const [password, setPassword] = useState('')
      const [generate, setGenerate] = useState(true)
    
      return (
        <div className="container">
          <div>
            <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
            <form>
              <div className="form-group m-4">
                <label htmlFor="url">Adres WWW:</label>
                <input id="url" type="text" className="form-control" placeholder="Adres strony WWW"
                  value={url} onChange={(event) => setUrl(event.target.value)} />
              </div>
              <div className="form-group m-4">
                <label htmlFor="login">Login:</label>
                <input id="login" type="text" className="form-control" placeholder="Twój login"
                  value={login} onChange={(event) => setLogin(event.target.value)} />
              </div>
              <div className="form-check mx-4">
                <input id="generate" type="checkbox" className="form-check-input"
                  checked={generate} onChange={(event) => setGenerate(event.target.checked)} />
                <label htmlFor="generate" className="form-check-label">Wygeneruj hasło</label>
              </div>
              <div className="form-group mx-4 mb-4">
                <label htmlFor="password">Hasło:</label>
                <input id="password" type="password" className="form-control" disabled={generate}
                  value={password} onChange={(event) => setPassword(event.target.value)} />
              </div>
              <button type="submit" className="btn btn-primary m-2">Zapisz</button>
            </form>
          </div>
          <div>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  24. Krok 24: Zatwierdzenie formularza: funkcja handleSubmit

    Kryteria: ,

    R.3.5 Zdefiniowana funkcja realizująca zatwierdzenie formularza, jest ona powiązana z formularzem zdarzeniem onSubmit lub onClick w React, a w Angular ngSubmit lub click

    R.1.3 Znaczące polskie lub angielskie nazewnictwo metody/funkcji

    ok. 3 min
    Polecenie z arkusza

    Elementy są wypełniane po zatwierdzeniu formularza przyciskiem.

    Podsumowanie ma pokazywać dane z chwili zatwierdzenia, a nie zmieniać się przy każdym wpisanym znaku. Dlatego potrzebny jest osobny stan saved - obiekt z kopią czterech wartości. Na start ma puste pola i generate: true, bo ilustracja 1 pokazuje już „Hasło automatycznie generowane”.

    Funkcję handleSubmit wiążemy z formularzem zdarzeniem onSubmit={handleSubmit} (bez nawiasów - przekazujemy funkcję, nie jej wynik). event.preventDefault() zatrzymuje domyślne zachowanie przeglądarki, czyli przeładowanie strony, które skasowałoby stan. Potem setSaved({ url, login, password, generate }) zapisuje bieżące wartości - zapis { url } to skrót od { url: url }.

    Typowe błędy

    onclick="handleSubmit()" w stylu HTML - klucz wprost wymienia przypisywanie metod do zdarzeń w ten sposób jako powód niezaliczenia CAŁEGO rezultatu 3. W React: onSubmit={handleSubmit} albo onClick={...}.

    web/src/App.jsx JSX zmienione: 7 linii
    import { useState } from 'react'
    
    function App() {
      const [url, setUrl] = useState('')
      const [login, setLogin] = useState('')
      const [password, setPassword] = useState('')
      const [generate, setGenerate] = useState(true)
      const [saved, setSaved] = useState({ url: '', login: '', password: '', generate: true })
    
      function handleSubmit(event) {
        event.preventDefault()
        setSaved({ url, login, password, generate })
      }
    
      return (
        <div className="container">
          <div>
            <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
            <form onSubmit={handleSubmit}>
              <div className="form-group m-4">
                <label htmlFor="url">Adres WWW:</label>
                <input id="url" type="text" className="form-control" placeholder="Adres strony WWW"
                  value={url} onChange={(event) => setUrl(event.target.value)} />
              </div>
              <div className="form-group m-4">
                <label htmlFor="login">Login:</label>
                <input id="login" type="text" className="form-control" placeholder="Twój login"
                  value={login} onChange={(event) => setLogin(event.target.value)} />
              </div>
              <div className="form-check mx-4">
                <input id="generate" type="checkbox" className="form-check-input"
                  checked={generate} onChange={(event) => setGenerate(event.target.checked)} />
                <label htmlFor="generate" className="form-check-label">Wygeneruj hasło</label>
              </div>
              <div className="form-group mx-4 mb-4">
                <label htmlFor="password">Hasło:</label>
                <input id="password" type="password" className="form-control" disabled={generate}
                  value={password} onChange={(event) => setPassword(event.target.value)} />
              </div>
              <button type="submit" className="btn btn-primary m-2">Zapisz</button>
            </form>
          </div>
          <div>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  25. Krok 25: Pierwszy paragraf: niebieski, wiodący, adres

    Kryteria: ,

    R.3.2 Do projektu dołączona jest biblioteka Bootstrap oraz sformatowane: przycisk klasą co najmniej: btn btn-primary pierwszy paragraf klasą co najmniej: lead text-primary

    R.3.8 Przy zaznaczonym checkbox, po wpisaniu adresu WWW i loginu oraz wciśnięciu przycisku pod przyciskiem są wypisane adres i login wprowadzone w formularzu oraz tekst: "Hasło automatycznie generowane" (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    ok. 2 min
    Polecenie z arkusza

    Trzy paragrafy ze stylem nadanym odpowiednimi klasami Bootstrap: margines wewnętrzny 0,5 rem. Dodatkowo tekst pierwszego paragrafu koloru niebieskiego, tekst jest wiodący (tabela 1). Paragraf pierwszy zawiera napis: „Adres: <adres>”.

    Blok drugi. Klasy z tabeli 1: p-2 - margines wewnętrzny (p = padding) rozmiaru 2, czyli 0,5 rem; text-primary - niebieski tekst; lead - tekst wiodący (większy, lżejszy). Treść to stały napis i wartość ze stanu: {saved.url}. Wpisz adres i kliknij „Zapisz” - pojawia się w paragrafie.

    web/src/App.jsx JSX zmienione: 1 linia
    import { useState } from 'react'
    
    function App() {
      const [url, setUrl] = useState('')
      const [login, setLogin] = useState('')
      const [password, setPassword] = useState('')
      const [generate, setGenerate] = useState(true)
      const [saved, setSaved] = useState({ url: '', login: '', password: '', generate: true })
    
      function handleSubmit(event) {
        event.preventDefault()
        setSaved({ url, login, password, generate })
      }
    
      return (
        <div className="container">
          <div>
            <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
            <form onSubmit={handleSubmit}>
              <div className="form-group m-4">
                <label htmlFor="url">Adres WWW:</label>
                <input id="url" type="text" className="form-control" placeholder="Adres strony WWW"
                  value={url} onChange={(event) => setUrl(event.target.value)} />
              </div>
              <div className="form-group m-4">
                <label htmlFor="login">Login:</label>
                <input id="login" type="text" className="form-control" placeholder="Twój login"
                  value={login} onChange={(event) => setLogin(event.target.value)} />
              </div>
              <div className="form-check mx-4">
                <input id="generate" type="checkbox" className="form-check-input"
                  checked={generate} onChange={(event) => setGenerate(event.target.checked)} />
                <label htmlFor="generate" className="form-check-label">Wygeneruj hasło</label>
              </div>
              <div className="form-group mx-4 mb-4">
                <label htmlFor="password">Hasło:</label>
                <input id="password" type="password" className="form-control" disabled={generate}
                  value={password} onChange={(event) => setPassword(event.target.value)} />
              </div>
              <button type="submit" className="btn btn-primary m-2">Zapisz</button>
            </form>
          </div>
          <div>
            <p className="lead text-primary p-2">Adres: {saved.url}</p>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  26. Krok 26: Drugi paragraf: login

    Kryteria:

    R.3.8 Przy zaznaczonym checkbox, po wpisaniu adresu WWW i loginu oraz wciśnięciu przycisku pod przyciskiem są wypisane adres i login wprowadzone w formularzu oraz tekst: "Hasło automatycznie generowane" (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    ok. 1 min
    Polecenie z arkusza

    Paragraf drugi zawiera napis: „Login: <login>”.

    Ten sam wzór, tylko z samą klasą p-2 - kolor i wyróżnienie dotyczą wyłącznie pierwszego paragrafu.

    web/src/App.jsx JSX zmienione: 1 linia
    import { useState } from 'react'
    
    function App() {
      const [url, setUrl] = useState('')
      const [login, setLogin] = useState('')
      const [password, setPassword] = useState('')
      const [generate, setGenerate] = useState(true)
      const [saved, setSaved] = useState({ url: '', login: '', password: '', generate: true })
    
      function handleSubmit(event) {
        event.preventDefault()
        setSaved({ url, login, password, generate })
      }
    
      return (
        <div className="container">
          <div>
            <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
            <form onSubmit={handleSubmit}>
              <div className="form-group m-4">
                <label htmlFor="url">Adres WWW:</label>
                <input id="url" type="text" className="form-control" placeholder="Adres strony WWW"
                  value={url} onChange={(event) => setUrl(event.target.value)} />
              </div>
              <div className="form-group m-4">
                <label htmlFor="login">Login:</label>
                <input id="login" type="text" className="form-control" placeholder="Twój login"
                  value={login} onChange={(event) => setLogin(event.target.value)} />
              </div>
              <div className="form-check mx-4">
                <input id="generate" type="checkbox" className="form-check-input"
                  checked={generate} onChange={(event) => setGenerate(event.target.checked)} />
                <label htmlFor="generate" className="form-check-label">Wygeneruj hasło</label>
              </div>
              <div className="form-group mx-4 mb-4">
                <label htmlFor="password">Hasło:</label>
                <input id="password" type="password" className="form-control" disabled={generate}
                  value={password} onChange={(event) => setPassword(event.target.value)} />
              </div>
              <button type="submit" className="btn btn-primary m-2">Zapisz</button>
            </form>
          </div>
          <div>
            <p className="lead text-primary p-2">Adres: {saved.url}</p>
            <p className="p-2">Login: {saved.login}</p>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  27. Krok 27: Trzeci paragraf: hasło albo informacja o generowaniu

    Kryteria: , ,

    R.3.8 Przy zaznaczonym checkbox, po wpisaniu adresu WWW i loginu oraz wciśnięciu przycisku pod przyciskiem są wypisane adres i login wprowadzone w formularzu oraz tekst: "Hasło automatycznie generowane" (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    R.3.9 Przy odznaczonym checkbox, po wpisaniu adresu WWW, loginu i hasła oraz wciśnięciu przycisku pod przyciskiem są wypisane adres, login i hasło wprowadzone w formularzu (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    R.3.1 Zdefiniowany jest jeden komponent, który zawiera nagłówek h1, formularz, trzy pola edycyjne, checkbox, przycisk "Zapisz" i trzy paragrafy, przynajmniej jedno pole edycyjne ma zdefiniowany atrybut placeholder, pole do wpisywania hasła jest typu password

    ok. 3 min
    Polecenie z arkusza

    Paragraf trzeci, gdy pole wyboru jest zaznaczone, zawiera napis „Hasło automatycznie generowane” (ilustracja 2), w przeciwnym wypadku napis „Hasło: <hasło>” (ilustracja 3).

    Wybór jednego z dwóch napisów zapisujemy operatorem warunkowym warunek ? gdyPrawda : gdyFałsz wewnątrz klamer. Warunkiem jest saved.generate - stan pola wyboru z chwili zapisu, więc samo kliknięcie pola wyboru nie zmienia podsumowania, dopiero „Zapisz”.

    Aplikacja jest kompletna. Sprawdź w podglądzie oba scenariusze z ilustracji: zaznaczone pole wyboru (adres, login i „Hasło automatycznie generowane” - R.3.8) i odznaczone z wpisanym hasłem (adres, login i hasło jawnie - R.3.9).

    web/src/App.jsx JSX zmienione: 3 linie
    import { useState } from 'react'
    
    function App() {
      const [url, setUrl] = useState('')
      const [login, setLogin] = useState('')
      const [password, setPassword] = useState('')
      const [generate, setGenerate] = useState(true)
      const [saved, setSaved] = useState({ url: '', login: '', password: '', generate: true })
    
      function handleSubmit(event) {
        event.preventDefault()
        setSaved({ url, login, password, generate })
      }
    
      return (
        <div className="container">
          <div>
            <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
            <form onSubmit={handleSubmit}>
              <div className="form-group m-4">
                <label htmlFor="url">Adres WWW:</label>
                <input id="url" type="text" className="form-control" placeholder="Adres strony WWW"
                  value={url} onChange={(event) => setUrl(event.target.value)} />
              </div>
              <div className="form-group m-4">
                <label htmlFor="login">Login:</label>
                <input id="login" type="text" className="form-control" placeholder="Twój login"
                  value={login} onChange={(event) => setLogin(event.target.value)} />
              </div>
              <div className="form-check mx-4">
                <input id="generate" type="checkbox" className="form-check-input"
                  checked={generate} onChange={(event) => setGenerate(event.target.checked)} />
                <label htmlFor="generate" className="form-check-label">Wygeneruj hasło</label>
              </div>
              <div className="form-group mx-4 mb-4">
                <label htmlFor="password">Hasło:</label>
                <input id="password" type="password" className="form-control" disabled={generate}
                  value={password} onChange={(event) => setPassword(event.target.value)} />
              </div>
              <button type="submit" className="btn btn-primary m-2">Zapisz</button>
            </form>
          </div>
          <div>
            <p className="lead text-primary p-2">Adres: {saved.url}</p>
            <p className="p-2">Login: {saved.login}</p>
            <p className="p-2">
              {saved.generate ? 'Hasło automatycznie generowane' : 'Hasło: ' + saved.password}
            </p>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  28. Krok 28: Folder webowa: web.zip i zmienione pliki

    Kryteria:

    R.4.9 Przygotowana jest dokumentacja w postaci płyty: oba projekty są spakowane oraz zmodyfikowane pliki są umieszczone na zewnątrz archiwów

    ok. 3 min
    Polecenie z arkusza

    Kod aplikacji przygotuj do nagrania na płytę. W folderze webowa powinno znaleźć się archiwum całego folderu projektu o nazwie web.zip oraz skopiowane pliki z kodem źródłowym, które były modyfikowane.

    Pakujemy folder web do web.zip, ale bez folderu node_modules - to tysiące plików bibliotek, które npm install odtwarza z package.json, a z nimi archiwum miałoby setki megabajtów i nagrywałoby się długo. Obok kopiujemy zmienione pliki: App.jsx i main.jsx.

    Podgląd strony bez zmian - taki jak po kroku 27.

  29. Etap 4: Dokumentacja aplikacji

    Kroki 29-32 · po tym etapie masz 32 z 32 kryteriów (pkt)

    Krok 29: Komentarz dokumentujący funkcję generate_password

    Kryteria: , , , ,

    R.4.1 Komentarz jest zgodny z Listingiem 1 arkusza egzaminacyjnego. Komentarz może być wieloliniowy lub kilka jednoliniowych, lub Docstrings (potrójny cudzysłów)

    R.4.2 W komentarzu umieszczona nazwa funkcji / metody oraz opisane jej działanie

    R.4.3 W komentarzu umieszczone nazwa i krótki opis wszystkich parametrów lub "brak", gdy nie ma parametrów

    R.4.4 W komentarzu umieszczona nazwa typu zwracanego i opis co jest zwracane lub "brak", gdy nic nie jest zwracane

    R.4.5 W komentarzu umieszczony numer zdającego

    ok. 4 min
    Polecenie z arkusza

    W kodzie źródłowym aplikacji konsolowej za pomocą komentarza utwórz jeden nagłówek do dowolnej funkcji / metody według wzoru z listingu 1. Komentarz powinien znaleźć się nad lub pod nazwą funkcji. W miejscu nawiasów <> należy podać odpowiednie opisy. W miejscu autor należy podać numer zdającego.

    W Pythonie dokumentację funkcji pisze się jako docstring - napis w potrójnym cudzysłowie zaraz pod linią def; klucz dopuszcza go wprost (R.4.1). Przepisujemy pola z listingu 1 i wypełniamy je zgodnie z kodem: nazwa i opis działania (R.4.2), parametry - funkcja nie ma żadnych, więc „brak” (R.4.3), zwracany typ str i co zwraca (R.4.4), autor (R.4.5). Na egzaminie w polu autor wpisujemy numer PESEL - tu stoi Bartosz Bryniarski.

    Opis musi zgadzać się z kodem: gdyby funkcja przyjmowała parametr, „brak” byłby błędem.

    Typowe błędy

    Komentarz przy funkcji pomocniczej insert_random_char też jest poprawny, ale wtedy trzeba opisać jej DWA parametry - wpisanie „brak” przy funkcji z parametrami nie spełnia R.4.3.

    konsola/main.py Python zmienione: 9 linii
    import random
    
    LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
    UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    DIACRITICS = "ąęłńóśżźĄĘŁŃÓŚŻŹ"
    DIGITS = "0123456789"
    SPECIAL = "!@#$%^&*()_+"
    
    
    def insert_random_char(password, characters):
        char = random.choice(characters)
        position = random.randint(0, len(password))
        return password[:position] + char + password[position:]
    
    
    def generate_password():
        """
        nazwa funkcji:       generate_password
        opis funkcji:        tworzy 12-znakowe hasło z 3 małych liter, 3 wielkich liter,
                             2 liter ze znakami diakrytycznymi, 2 cyfr i 2 znaków
                             specjalnych, każdy znak wstawiając w losowe miejsce hasła
        parametry:           brak
        zwracany typ i opis: str - wygenerowane hasło
        autor:               Bartosz Bryniarski
        """
        password = ""
        for _ in range(3):
            password = insert_random_char(password, LOWERCASE)
        for _ in range(3):
            password = insert_random_char(password, UPPERCASE)
        for _ in range(2):
            password = insert_random_char(password, DIACRITICS)
        for _ in range(2):
            password = insert_random_char(password, DIGITS)
        for _ in range(2):
            password = insert_random_char(password, SPECIAL)
        return password
    
    
    print("Generator bezpiecznych haseł - pięć haseł po 12 znaków:")
    for number in range(1, 6):
        print(f"Hasło {number}: {generate_password()}")
    Wynik programu po tym kroku
    > python main.py
    Generator bezpiecznych haseł - pięć haseł po 12 znaków:
    Hasło 1: ^)vOQ1ŁTśt0u
    Hasło 2: Óx1tCOvś@D$4
    Hasło 3: oŁbjŃU&@C67N
    Hasło 4: +nĘ)p5QfHUĘ3
    Hasło 5: bmDKÓbŁ79*&W
  30. Krok 30: Zrzuty konsola1 i konsola2

    Kryteria: , ,

    R.2.6 Program uruchamia się w konsoli, co jest udokumentowane zrzutem ekranu

    R.1.6 Podjęta próba uruchomienia kodu, na zrzucie widoczny jest uruchomiony program lub błędy jego kompilacji / interpretacji

    R.4.6 Zapisany przynajmniej jeden zrzut ekranu z uruchomienia lub kompilacji aplikacji konsolowej

    ok. 3 min
    Polecenie z arkusza

    Wykonaj zrzuty ekranu dokumentujące uruchomienie aplikacji utworzonych podczas egzaminu. Zrzuty powinny obejmować cały obszar ekranu monitora z widocznym paskiem zadań. Jeżeli aplikacja uruchamia się, na zrzucie należy umieścić okno z wynikiem działania programu oraz otwarte środowisko programistyczne z projektem lub okno terminala z kompilacją projektu. Aplikacja konsolowa – dowolna liczba zrzutów nazwanych konsola1, konsola2, ...

    Uruchamiamy program w PyCharm i robimy zrzut całego ekranu (Print Screen) z oknem PyCharm, wynikiem w panelu Run i paskiem zadań - konsola1.png. Program nie pyta o nic użytkownika, więc jeden zrzut dokumentuje całą interakcję; drugi (konsola2.png) po ponownym uruchomieniu pokazuje, że hasła są za każdym razem inne. Zrzuty zapisujemy w folderze dokumentacja.

    Wynik programu po tym kroku
    > python main.py
    Generator bezpiecznych haseł - pięć haseł po 12 znaków:
    Hasło 1: ^)vOQ1ŁTśt0u
    Hasło 2: Óx1tCOvś@D$4
    Hasło 3: oŁbjŃU&@C67N
    Hasło 4: +nĘ)p5QfHUĘ3
    Hasło 5: bmDKÓbŁ79*&W
    
    > python main.py
    Generator bezpiecznych haseł - pięć haseł po 12 znaków:
    Hasło 1: 8r!Hń*ĄKN7hf
    Hasło 2: a*WcK*4X7rżÓ
    Hasło 3: 1I*D!rmwCŚŚ8
    Hasło 4: @có)bÓ4E7eHS
    Hasło 5: ŹdXfb0ŻD#Q8)
  31. Krok 31: Zrzuty web1, web2, web3

    Kryteria: ,

    R.3.6 Aplikacja jest interpretowana bez błędów. Układ elementów w stanie początkowym jest zgodny z obrazem 1 z arkusza egzaminacyjnego, co udokumentowane zrzutem ekranu

    R.4.7 Zapisany przynajmniej jeden zrzut ekranu z uruchomienia aplikacji webowej lub z błędami interpretacji, na zrzucie widoczne jest środowisko, w którym powstała aplikacja

    ok. 4 min
    Polecenie z arkusza

    Aplikacja webowa – dowolna liczba zrzutów nazwanych web1, web2, ... (stan początkowy, interakcje z zaznaczonym i odznaczonym polem wyboru)

    npm run dev w terminalu i adres http://localhost:5173 w przeglądarce. Trzy zrzuty całego ekranu, na każdym przeglądarka z aplikacją i środowisko z projektem albo terminal z działającym serwerem:

    • web1.png - stan początkowy (ilustracja 1, R.3.6),
    • web2.png - wpisany adres i login, pole wyboru zaznaczone, po „Zapisz” (ilustracja 2),
    • web3.png - pole wyboru odznaczone, wpisane hasło, po „Zapisz” (ilustracja 3).

    Podgląd obok to aplikacja z poprzedniego kroku - możesz w niej przejść te trzy stany.

    Typowe błędy

    Zrzut samego okna przeglądarki, bez środowiska albo terminala, nie spełnia R.4.7 - na zrzucie ma być widać, gdzie powstała aplikacja.

    Podgląd strony po tym kroku
  32. Krok 32: Plik egzamin

    Kryteria:

    R.4.8 Dokumentacja zawiera: nazwę systemu operacyjnego, nazwy środowisk programistycznych, nazwy języków programowania

    ok. 2 min
    Polecenie z arkusza

    Utwórz plik tekstowy i nazwij go egzamin. Dokument powinien zawierać informacje o narzędziach wykorzystanych na egzaminie: Nazwę systemu operacyjnego, Nazwy środowisk programistycznych, Nazwy języków programowania. Zrzuty ekranu i dokument umieść w folderze dokumentacja.

    Plik tekstowy egzamin w folderze dokumentacja (w tym arkuszu - nie w folderze głównym, jak bywa w innych). Trzy informacje: system, środowiska i języki - wpisujemy to, czego naprawdę używaliśmy. React to biblioteka, a językiem jest JavaScript (z JSX); warto napisać jedno i drugie.

    egzamin.txt Tekst nowy plik
    System operacyjny: Windows 11
    Środowiska programistyczne: PyCharm (aplikacja konsolowa), Visual Studio Code z Node.js i Vite (aplikacja webowa)
    Języki programowania: Python (aplikacja konsolowa), JavaScript z JSX - biblioteka React (aplikacja webowa)

    Podgląd strony bez zmian - taki jak po kroku 31.

  33. Etap 5: Dobre praktyki (poza kluczem)

    Kroki 33-35 · po tym etapie masz 32 z 32 kryteriów (pkt)

    Krok 33: Konsola: moduł secrets zamiast random

    ok. 3 min
    Polecenie z arkusza

    Za pomocą narzędzi do tworzenia aplikacji konsolowych zaimplementuj program do tworzenia bezpiecznych haseł.

    Moduł random jest dobry do gier i symulacji, ale jego liczby są przewidywalne: kto pozna stan generatora (np. z kilkuset wyników), odtworzy następne. Do haseł, tokenów i kluczy dokumentacja Pythona każe używać modułu secrets, który korzysta z bezpiecznego źródła losowości systemu. Zamiana jest prosta: secrets.choice w miejsce random.choice, a secrets.randbelow(n) (liczba od 0 do n-1) w miejsce random.randint(0, n - 1) - stąd len(password) + 1. Stare linie zostają w komentarzu.

    Na egzaminie wystarczy wersja wcześniejsza - klucz tego nie sprawdza. Ale skoro program ma tworzyć bezpieczne hasła, to jest właściwe narzędzie.

    konsola/main.py Python zmienione: 6 linii
    import secrets
    # import random
    
    LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
    UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    DIACRITICS = "ąęłńóśżźĄĘŁŃÓŚŻŹ"
    DIGITS = "0123456789"
    SPECIAL = "!@#$%^&*()_+"
    
    
    def insert_random_char(password, characters):
        char = secrets.choice(characters)
        position = secrets.randbelow(len(password) + 1)
        # char = random.choice(characters)
        # position = random.randint(0, len(password))
        return password[:position] + char + password[position:]
    
    
    def generate_password():
        """
        nazwa funkcji:       generate_password
        opis funkcji:        tworzy 12-znakowe hasło z 3 małych liter, 3 wielkich liter,
                             2 liter ze znakami diakrytycznymi, 2 cyfr i 2 znaków
                             specjalnych, każdy znak wstawiając w losowe miejsce hasła
        parametry:           brak
        zwracany typ i opis: str - wygenerowane hasło
        autor:               Bartosz Bryniarski
        """
        password = ""
        for _ in range(3):
            password = insert_random_char(password, LOWERCASE)
        for _ in range(3):
            password = insert_random_char(password, UPPERCASE)
        for _ in range(2):
            password = insert_random_char(password, DIACRITICS)
        for _ in range(2):
            password = insert_random_char(password, DIGITS)
        for _ in range(2):
            password = insert_random_char(password, SPECIAL)
        return password
    
    
    print("Generator bezpiecznych haseł - pięć haseł po 12 znaków:")
    for number in range(1, 6):
        print(f"Hasło {number}: {generate_password()}")
    Wynik programu po tym kroku
    > python main.py
    Generator bezpiecznych haseł - pięć haseł po 12 znaków:
    Hasło 1: _!ż7Ś3bVBkmK
    Hasło 2: ś$r09ÓCkcOO@
    Hasło 3: v!Uą8BWa^w4ł
    Hasło 4: 9oLżxA@JĄ8@m
    Hasło 5: ńRt5+c_7XdźK
  34. Krok 34: Konsola: program główny w funkcji main

    ok. 2 min
    Polecenie z arkusza

    Aplikacja w programie głównym generuje 5 haseł i wyświetla je ze znaczącym komunikatem.

    Kod na poziomie pliku wykonuje się także przy imporcie - gdyby inny program (albo plik z testami) zrobił from main import generate_password, najpierw wypisałby pięć haseł. Warunek if __name__ == "__main__": jest prawdziwy tylko wtedy, gdy plik uruchomiono bezpośrednio, więc program główny przenosimy do funkcji main() i wywołujemy ją pod tym warunkiem. Wynik działania jest taki sam.

    Na egzaminie wystarczy wersja wcześniejsza - klucz tego nie sprawdza.

    konsola/main.py Python zmienione: 8 linii
    import secrets
    # import random
    
    LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
    UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    DIACRITICS = "ąęłńóśżźĄĘŁŃÓŚŻŹ"
    DIGITS = "0123456789"
    SPECIAL = "!@#$%^&*()_+"
    
    
    def insert_random_char(password, characters):
        char = secrets.choice(characters)
        position = secrets.randbelow(len(password) + 1)
        # char = random.choice(characters)
        # position = random.randint(0, len(password))
        return password[:position] + char + password[position:]
    
    
    def generate_password():
        """
        nazwa funkcji:       generate_password
        opis funkcji:        tworzy 12-znakowe hasło z 3 małych liter, 3 wielkich liter,
                             2 liter ze znakami diakrytycznymi, 2 cyfr i 2 znaków
                             specjalnych, każdy znak wstawiając w losowe miejsce hasła
        parametry:           brak
        zwracany typ i opis: str - wygenerowane hasło
        autor:               Bartosz Bryniarski
        """
        password = ""
        for _ in range(3):
            password = insert_random_char(password, LOWERCASE)
        for _ in range(3):
            password = insert_random_char(password, UPPERCASE)
        for _ in range(2):
            password = insert_random_char(password, DIACRITICS)
        for _ in range(2):
            password = insert_random_char(password, DIGITS)
        for _ in range(2):
            password = insert_random_char(password, SPECIAL)
        return password
    
    
    def main():
        print("Generator bezpiecznych haseł - pięć haseł po 12 znaków:")
        for number in range(1, 6):
            print(f"Hasło {number}: {generate_password()}")
    
    
    if __name__ == "__main__":
        main()
    Wynik programu po tym kroku
    > python main.py
    Generator bezpiecznych haseł - pięć haseł po 12 znaków:
    Hasło 1: AźeŻ*S_0Rrr3
    Hasło 2: żLuŁ0aqQZ!5&
    Hasło 3: jMąy)44MMŚ@f
    Hasło 4: v68iAYĄS_gś!
    Hasło 5: 8ę$abH0Gł+xG
  35. Krok 35: React: jeden stan formularza i jedna funkcja handleChange

    ok. 4 min
    Polecenie z arkusza

    Kod aplikacji powinien być zapisany czytelnie, z zachowaniem zasad czystego formatowania, należy stosować znaczące nazewnictwo, polskie albo angielskie.

    Cztery osobne stany i cztery prawie identyczne funkcje onChange to kod, który rośnie z każdym nowym polem. Typowy zapis formularzy w React: jeden obiekt stanu form i jedna funkcja handleChange. Każde pole dostaje atrybut name równy nazwie klucza w obiekcie; funkcja odczytuje go z event.target i podmienia tylko tę wartość - dla pola wyboru bierze checked, dla pozostałych value. Zapis { ...form, [name]: ... } tworzy nowy obiekt: kopię starego z jedną zmienioną właściwością (React wymaga nowego obiektu, żeby zauważyć zmianę). Zatwierdzenie to teraz po prostu setSaved(form). Działanie aplikacji jest takie samo.

    Na egzaminie wystarczy wersja wcześniejsza - klucz tego nie sprawdza. Komponent nadal jest jeden, jak wymaga arkusz.

    web/src/App.jsx JSX zmienione: 13 linii
    import { useState } from 'react'
    
    function App() {
      const [form, setForm] = useState({ url: '', login: '', password: '', generate: true })
      const [saved, setSaved] = useState(form)
    
      function handleChange(event) {
        const { name, type, value, checked } = event.target
        setForm({ ...form, [name]: type === 'checkbox' ? checked : value })
      }
    
      function handleSubmit(event) {
        event.preventDefault()
        setSaved(form)
      }
    
      return (
        <div className="container">
          <div>
            <h1>Menedżer haseł - wykonał Bartosz Bryniarski</h1>
            <form onSubmit={handleSubmit}>
              <div className="form-group m-4">
                <label htmlFor="url">Adres WWW:</label>
                <input id="url" type="text" className="form-control" placeholder="Adres strony WWW"
                  name="url" value={form.url} onChange={handleChange} />
              </div>
              <div className="form-group m-4">
                <label htmlFor="login">Login:</label>
                <input id="login" type="text" className="form-control" placeholder="Twój login"
                  name="login" value={form.login} onChange={handleChange} />
              </div>
              <div className="form-check mx-4">
                <input id="generate" type="checkbox" className="form-check-input"
                  name="generate" checked={form.generate} onChange={handleChange} />
                <label htmlFor="generate" className="form-check-label">Wygeneruj hasło</label>
              </div>
              <div className="form-group mx-4 mb-4">
                <label htmlFor="password">Hasło:</label>
                <input id="password" type="password" className="form-control" disabled={form.generate}
                  name="password" value={form.password} onChange={handleChange} />
              </div>
              <button type="submit" className="btn btn-primary m-2">Zapisz</button>
            </form>
          </div>
          <div>
            <p className="lead text-primary p-2">Adres: {saved.url}</p>
            <p className="p-2">Login: {saved.login}</p>
            <p className="p-2">
              {saved.generate ? 'Hasło automatycznie generowane' : 'Hasło: ' + saved.password}
            </p>
          </div>
        </div>
      )
    }
    
    export default App
    Podgląd strony po tym kroku
  36. Etap 6: Oddanie pracy

    Krok 36 · po tym etapie masz 32 z 32 kryteriów (pkt)

    Krok 36: Nagranie płyty

    Kryteria:

    R.4.9 Przygotowana jest dokumentacja w postaci płyty: oba projekty są spakowane oraz zmodyfikowane pliki są umieszczone na zewnątrz archiwów

    ok. 5 min
    Polecenie z arkusza

    UWAGA: Nagraj płytę z rezultatami pracy. W folderze z numerem zdającego powinny się znajdować trzy foldery: dokumentacja, konsolowa i webowa. W folderze dokumentacja: pliki ze zrzutami oraz plik egzamin. W folderze konsolowa: spakowany cały projekt aplikacji konsolowej, modyfikowane pliki źródłowe, plik wykonywalny, jeżeli istnieje. W folderze webowa: spakowany cały projekt aplikacji webowej, modyfikowane pliki źródłowe.

    Sprawdzamy zawartość przed nagraniem:

    • dokumentacja: konsola1.png, konsola2.png, web1.png - web3.png, egzamin,
    • konsolowa: konsola.zip (spakowany po dopisaniu komentarza), main.py,
    • webowa: web.zip (bez node_modules), App.jsx, main.jsx.

    Oba projekty spakowane, zmienione pliki obok archiwów (R.4.9). Nagrywamy, sprawdzamy odczyt płyty, opisujemy ją numerem zdającego. Pusta lub nieczytelna płyta to zero punktów za wszystkie rezultaty.

    Podgląd strony bez zmian - taki jak po kroku 35.

Podgląd

To jedno z możliwych rozwiązań. Egzaminator ocenia spełnienie kryteriów z klucza odpowiedzi, a nie zgodność z tym kodem - inna, poprawna droga też daje punkty.

Autor rozwiązania: . Licencja CC BY-NC 4.0 - kopiowanie i przeróbki do celów niekomercyjnych, z podaniem autora i źródła (link do tej strony). Treść arkusza i klucza oceniania pochodzi z CKE.