--- phase: 01-reservation-form-plugin plan: 01 type: execute wave: 1 depends_on: [] files_modified: - wp-content/plugins/carei-reservation/carei-reservation.php - wp-content/plugins/carei-reservation/includes/class-softra-api.php - wp-content/plugins/carei-reservation/includes/class-rest-proxy.php - wp-content/plugins/carei-reservation/includes/class-elementor-widget.php autonomous: true --- ## Goal Utworzyć plugin WordPress `carei-reservation` z: 1. Klasą proxy do Softra Rent API (autoryzacja JWT + cache tokenu + endpointy floty) 2. WP REST API endpoints jako proxy (frontend → WP → Softra) 3. Zarejestrowanym widgetem Elementor (shell z przyciskiem triggerującym modal) ## Purpose Backend i szkielet pluginu to fundament — bez tego nie ma formularza. API proxy chroni credentials (nigdy nie wystawiane na frontend) i zapewnia cache tokenu JWT. ## Output Nowy plugin `wp-content/plugins/carei-reservation/` z 4 plikami PHP, gotowy do aktywacji. ## Project Context @.paul/PROJECT.md @.paul/ROADMAP.md ## Source Files @.env (credentials Softra API) @docs/rent-api-01-autoryzacja-i-flota.md (endpointy: auth, branches, car classes, models, prices) @docs/rent-api-02-klienci-i-konta.md (customer/add, agreement/def/list) @docs/rent-api-03-rezerwacje-i-platnosci.md (makebooking, confirm, pricingSummary) @docs/figma-formularz/README.md (specyfikacja UI) @wp-content/plugins/elementor-addon/elementor-addon.php (wzorzec rejestracji widgetów) ## AC-1: Plugin aktywuje się bez błędów ```gherkin Given plugin carei-reservation jest w wp-content/plugins/ When administrator aktywuje plugin w panelu WP Then plugin ładuje się bez PHP errors/warnings And widget "Carei Reservation" pojawia się w panelu Elementor ``` ## AC-2: API Proxy — autoryzacja i cache tokenu ```gherkin Given credentials Softra API są w .env When proxy wykonuje pierwsze żądanie do Softra Then pobiera token JWT przez POST /account/auth And cachuje token w WP transient na 50 minut (token ważny 60min) And kolejne żądania używają cached tokenu ``` ## AC-3: REST API proxy zwraca dane oddziałów ```gherkin Given token JWT jest aktywny When frontend wywołuje GET /wp-json/carei/v1/branches Then proxy zwraca listę oddziałów z Softra /branch/list And odpowiedź zawiera name, description, city, street ``` ## AC-4: REST API proxy zwraca klasy pojazdów i cennik ```gherkin Given token JWT jest aktywny When frontend wywołuje POST /wp-json/carei/v1/car-classes z dateFrom, dateTo, branchName Then proxy zwraca listę klas z Softra /car/class/list When frontend wywołuje POST /wp-json/carei/v1/pricelist z category, dateFrom, dateTo, pickUpLocation Then proxy zwraca cennik z additionalItems z Softra /pricelist/list ``` ## AC-5: Widget Elementor renderuje przycisk-trigger ```gherkin Given widget "Carei Reservation" jest dodany na stronę w Elementorze When strona jest wyświetlana na froncie Then widoczny jest przycisk "Złóż zapytanie o rezerwację" (stylowany wg Figmy) And kliknięcie przycisku otwiera pusty modal (placeholder na krok 2 — Form UI) ``` Task 1: Główny plik pluginu + klasa Softra API wp-content/plugins/carei-reservation/carei-reservation.php, wp-content/plugins/carei-reservation/includes/class-softra-api.php **carei-reservation.php:** - Plugin header (Plugin Name: Carei Reservation, Version: 1.0.0) - Załaduj .env z ABSPATH (parsuj ręcznie, format: `key: value` — NIE `KEY=value`) - Require includes/*.php - Hook `plugins_loaded` → inicjalizacja - Hook `elementor/widgets/register` → rejestracja widgetu **class-softra-api.php:** - Klasa `Carei_Softra_API` (singleton) - Constructor: przyjmuje url, username, password z .env - `get_token()`: sprawdza WP transient `carei_softra_token`, jeśli brak → POST /account/auth, cache na 50 min - `request($method, $endpoint, $body = null)`: generyczna metoda z wp_remote_request, Authorization Bearer header, JSON body - Metody publiczne: - `get_branches()` → GET /branch/list - `get_car_classes($dateFrom, $dateTo, $branchName)` → POST /car/class/list - `get_car_models($dateFrom, $dateTo, $branchName, $category)` → POST /car/model/list?includeBrandDetails=true - `get_pricelist($category, $dateFrom, $dateTo, $pickUpLocation, $lang='pl', $currency='PLN')` → POST /pricelist/list - `get_pricing_summary($params)` → POST /rent/princingSummary - `add_customer($data)` → POST /customer/add - `make_booking($data)` → POST /rent/makebooking - `confirm_booking($reservationId)` → POST /rent/confirm - `get_agreements()` → GET /agreement/def/list - Error handling: zwracaj WP_Error przy błędach HTTP lub pustym tokenie Sprawdź: `php -l wp-content/plugins/carei-reservation/carei-reservation.php` i `php -l wp-content/plugins/carei-reservation/includes/class-softra-api.php` — brak syntax errors. AC-1 częściowo (plugin ładuje się), AC-2 (autoryzacja + cache) Task 2: WP REST API proxy endpoints wp-content/plugins/carei-reservation/includes/class-rest-proxy.php **class-rest-proxy.php:** - Klasa `Carei_REST_Proxy` - Hook `rest_api_init` → rejestracja routes w namespace `carei/v1` - Endpoints: 1. `GET /branches` → `Carei_Softra_API::get_branches()` 2. `POST /car-classes` (params: dateFrom, dateTo, branchName) → `get_car_classes()` 3. `POST /car-models` (params: dateFrom, dateTo, branchName, category) → `get_car_models()` 4. `POST /pricelist` (params: category, dateFrom, dateTo, pickUpLocation) → `get_pricelist()` 5. `POST /pricing-summary` (params: full booking params) → `get_pricing_summary()` 6. `POST /customer` (params: customer data) → `add_customer()` 7. `POST /booking` (params: booking data) → `make_booking()` 8. `POST /booking/confirm` (params: reservationId) → `confirm_booking()` 9. `GET /agreements` → `get_agreements()` - Permission callback: `__return_true` dla GET, nonce check dla POST (wp_rest nonce) - Sanitization: `sanitize_text_field` na wszystkich string params - Odpowiedź: `new WP_REST_Response($data, 200)` lub `WP_Error` `php -l wp-content/plugins/carei-reservation/includes/class-rest-proxy.php` — brak syntax errors. AC-3 (branches endpoint), AC-4 (car-classes + pricelist endpoints) Task 3: Elementor Widget shell z przyciskiem-triggerem wp-content/plugins/carei-reservation/includes/class-elementor-widget.php **class-elementor-widget.php:** - Klasa `Carei_Reservation_Widget extends \Elementor\Widget_Base` - `get_name()`: 'carei-reservation' - `get_title()`: 'Carei Reservation' - `get_icon()`: 'eicon-form-horizontal' - `get_categories()`: ['general'] - `get_style_depends()`: ['carei-reservation-css'] (placeholder) - `get_script_depends()`: ['carei-reservation-js'] (placeholder) - `register_controls()`: - Section: button_content → kontrolka tekstu przycisku (default: "Złóż zapytanie o rezerwację") - `render()`: - Przycisk HTML z klasą `carei-reservation-trigger` - Inline style: czerwony (#FF0000) background, biały tekst, Albert Sans font, border-radius 8px - Ikona strzałki (SVG inline) przed tekstem - Pusty div `
` jako mount point dla modala - Inline `