Files
Kaloyan Danchev 87dadf1def Initial commit: booking-ewa v1.0.0
Embedded Web App for EV charging slot bookings. Express backend with JWT
auth and AMPECO Public API proxy. React SPA with booking CRUD, availability
checking, and runtime design token theming.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 19:34:13 +02:00

9.9 KiB

User Guide

How the EWA is Embedded

The Booking EWA (Embedded Web App) is loaded inside a mobile app's WebView. The mobile app generates a signed JWT containing the user's session data and passes it via the X-Payload HTTP header when loading the EWA URL.

The flow:

  1. The mobile app creates a JWT (signed with HS256 using a shared secret) containing user identity, operator config, theme preference, and optional design tokens.
  2. The WebView loads the EWA URL, attaching the JWT in the X-Payload request header.
  3. The Express backend verifies the JWT and extracts session data.
  4. The React frontend fetches /api/session to retrieve this session data.
  5. The frontend applies the theme (LIGHT/DARK + custom design tokens) and renders the UI.

Screens

1. Home (/#/)

The landing screen of the application. Displays:

  • Title: "Bookings"
  • Upcoming Bookings: Up to 3 active bookings (status accepted or reserved) with startAt in the future, displayed as BookingCard components. Each card shows the location ID, time range, and status badge. Tapping a card navigates to the Booking Detail screen.
  • Empty State: When no upcoming bookings exist, shows a calendar icon with "No Bookings Yet" and a prompt to book a charging slot.
  • "Book a Charger" Button: Primary action that navigates to the Create Booking screen.

2. Create Booking (/#/create)

A two-step flow for creating a new booking.

Step 1 -- Select Time and Location:

  • Location ID (number input): The ID of the charging location. Enter the numeric location ID from the AMPECO platform.
  • Start Time (datetime picker): Minimum value is the current time.
  • End Time (datetime picker): Minimum value is the selected start time.
  • "Check Availability" button: Sends a POST request to check availability at the specified location for the given time range. Advances to Step 2 on success.

Step 2 -- Confirm Booking:

  • Available Slots: Displays EVSE-grouped time slots returned by the availability check. Each EVSE shows its ID and a grid of selectable time windows. Tapping a slot populates the start/end time fields.
  • EVSE Criteria (collapsible section): Optional filters for the booking request:
    • Current Type: AC or DC (dropdown)
    • Min Power (kW)
    • Max Power (kW)
    • Connector Type (text)
  • "Confirm Booking" button: Submits a create booking request.

Result Screen:

After submission, displays one of:

  • Approved: Green check icon, "Booking Confirmed" message, and a "View Booking" button that navigates to the booking detail.
  • Rejected: Red X icon, "Booking Rejected" message with the rejection reason, and a "Try Again" button to reset the form.

3. My Bookings (/#/bookings)

Lists the user's bookings with a tab bar:

  • Upcoming Tab: Bookings with status accepted or reserved where startAt is in the future.
  • Past Tab: Bookings where endAt is in the past (all statuses).

Each booking is rendered as a BookingCard. Tapping a card navigates to the Booking Detail screen.

Shows an empty state with a calendar icon when no bookings exist for the selected tab.

4. Booking Detail (/#/bookings/:id)

Displays full details of a single booking:

  • Location: Location ID
  • Time Range: Formatted start and end times
  • Status: Color-coded badge (green for accepted, blue for reserved, gray for completed, red for cancelled/failed, amber for no-show)
  • EVSEs: List of booked EVSE IDs (if any)
  • Access Methods: List of access methods (if any)
  • Session: Linked charging session ID (if any)

Actions (shown only when status is accepted or reserved):

  • "Update Booking" button: Navigates to the Update Booking screen.
  • "Cancel Booking" button: Opens a confirmation bottom sheet with "Yes, Cancel" and "Close" options.

The detail view polls the API every 30 seconds to keep the status current. Polling pauses when the browser tab is hidden and stops when the booking reaches a terminal status.

5. Update Booking (/#/bookings/:id/update)

Allows modifying the time range of an existing booking:

  • Displays a read-only summary of the booking (location ID and booking ID).
  • Start Time and End Time datetime pickers, pre-populated with the current booking times.
  • "Save Changes" button: Submits an update booking request.
  • On success: Shows a green "Booking Confirmed" banner.
  • On rejection: Shows the rejection reason or validation errors.

Development Mode

Dev Fallback Session

When NODE_ENV=development (set in .env), the backend automatically provides a fallback session if no JWT is present:

{
  "userId": 775,
  "operatorId": 1,
  "appLanguage": "en",
  "appCountry": "US",
  "appTheme": "LIGHT"
}

This means you can open http://localhost:5173 directly in a browser without needing a JWT. The app will use user ID 775 by default.

UserIdFallback Screen

If the session has userId: null (which can happen with certain JWT configurations), the frontend displays a "User Identification" screen prompting you to enter a numeric user ID. This screen is shown instead of the main app.

Generating a Dev JWT

In development mode, the backend exposes a POST /dev/jwt endpoint for generating JWT tokens with custom session data.

Request:

curl -X POST http://localhost:3001/dev/jwt \
  -H "Content-Type: application/json" \
  -d '{
    "userId": 123,
    "operatorId": 2,
    "appTheme": "DARK"
  }'

Any field from SessionData can be overridden. Omitted fields use the default dev values.

Response:

{
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

Using the token:

Pass it as the X-Payload header in subsequent requests:

curl http://localhost:5173/api/session \
  -H "X-Payload: eyJhbGciOiJIUzI1NiIs..."

Or use it in browser-based testing by setting the header in your HTTP client or browser extension.

The generated JWT has a 24-hour expiry, uses HS256, and is signed with the JWT_SECRET from .env.


Environment Variables Reference

Variable Required Default Description
API_BASE_URL Yes "" Base URL of the AMPECO public API (e.g., https://instance.charge.ampeco.tech/public-api)
API_TOKEN Yes "" Bearer token for authenticating backend requests to the public API
JWT_SECRET Yes dev-secret-for-local-testing Secret key for HS256 JWT verification. Must match the mobile app's signing key.
PORT No 3001 Port for the Express backend server
NODE_ENV No production Set to development for dev fallback session and /dev/jwt endpoint

Booking Lifecycle

Statuses

Status Color Description
accepted Green Booking request approved, slot is reserved for the user
reserved Blue Booking is actively reserved at the charge point
completed Gray Booking period ended normally
cancelled Red Booking was cancelled by the user or system
no-show Amber User did not show up during the booking window
failed Red Booking request or reservation failed

Status Transitions

stateDiagram-v2
    [*] --> accepted : Booking request approved
    accepted --> reserved : Charge point reserved
    accepted --> cancelled : User cancels
    accepted --> failed : System failure
    reserved --> completed : Booking period ends
    reserved --> cancelled : User cancels
    reserved --> no_show : User does not show up
    completed --> [*]
    cancelled --> [*]
    no_show --> [*]
    failed --> [*]

Terminal statuses: completed, cancelled, no-show, failed. Once a booking reaches a terminal status, the detail view stops polling and the update/cancel actions are hidden.

Actions by Status

Status Can Update Can Cancel
accepted Yes Yes
reserved Yes Yes
completed No No
cancelled No No
no-show No No
failed No No

Booking Request Flow

All mutations (create, update, cancel) go through the booking request mechanism:

  1. The frontend submits a BookingRequest with a type field (create, update, or cancel).
  2. The backend proxies this to POST /resources/booking-requests/v1.0.
  3. The API returns a BookingRequest response with status: 'approved' or status: 'rejected'.
  4. For create requests, an approved response includes a bookingId for the newly created booking.
  5. For update requests, the booking is modified in-place.
  6. For cancel requests, the booking transitions to cancelled status.

Error Handling

The frontend handles errors at multiple levels:

  • API Client (src/api/client.ts): Throws ApiRequestError with the HTTP status code and parsed response body. Provides convenience getters for common status codes (401, 403, 404, 422).
  • Validation Errors (422): The validationErrors getter extracts the errors object from the response body, which maps field names to arrays of error messages. The Create and Update booking forms display these inline.
  • Network/Proxy Errors: The backend returns 502 Bad Gateway when the upstream API is unreachable.
  • Session Errors: If /api/session fails, the app displays an error screen with a "Retry" button that reloads the page.