Destination Module — Frontend Integration Guide

Version: 1.0
Backend Phase: 01-destination-module (Complete)
Last Updated: 2026-04-30

This document maps the Balsamiq wireframes to actual API endpoints, showing where, when, and how each API is used in the admin panel.


Table of Contents

  1. Wireframe-to-API Mapping Overview
  2. Screen 1: Destination List
  3. Screen 2: Add/Edit Destination — Hero & Intro Tab
  4. Screen 2: Overview Facts Tab
  5. Screen 2: Highlights Tab
  6. Screen 2: Climate Tab
  7. Screen 2: Getting There Tab
  8. Screen 2: Docs & Health Tab
  9. Common Patterns
  10. Error Handling

1. Wireframe-to-API Mapping Overview

Wireframe Screen Tab / Action API Endpoint Method
Destination List Load list GET /destinations Public
Destination List Filter by type GET /destinations?type=country Public
Destination List Toggle status PATCH /destinations/:id/status Auth
Destination List Soft delete DELETE /destinations/:id Auth
Add Destination Load parent dropdown GET /destinations/parent-options?type=country Public
Add Destination Save (Hero & Intro) POST /destinations Auth
Edit Destination Load full data GET /destinations/:id Public
Edit Destination Save Hero & Intro PATCH /destinations/:id Auth
Edit Destination Save Overview Facts PUT /destinations/:id/overview Auth
Edit Destination Save Highlights PUT /destinations/:id/sections/highlights Auth
Edit Destination Upload image in Highlights POST /destinations/:id/sections/highlights/media Auth
Edit Destination Save Climate PUT /destinations/:id/sections/climate Auth
Edit Destination Save Getting There PUT /destinations/:id/sections/getting_there Auth
Edit Destination Save Docs & Health PUT /destinations/:id/sections/docs_health Auth

2. Screen 1: Destination List

2.1 Load Destination List (Initial Page Load)

When: On initial page load or when filters change.

API:

GET /destinations?type=&parentId=&status=

Query Parameters (all optional):

Param Type Description
type enum world_area, country, region
parentId number Filter by parent destination ID
status enum published, unpublished

Response:

{
  "message": "SUC_DESTINATION_LIST",
  "data": {
    "destinations": [
      {
        "id": 1,
        "name": "Europe",
        "type": "world_area",
        "status": "published",
        "updatedAt": "2026-04-30T10:00:00.000Z"
      },
      {
        "id": 2,
        "name": "France",
        "type": "country",
        "status": "published",
        "updatedAt": "2026-04-30T09:30:00.000Z"
      }
    ],
    "totalCount": 2
  }
}

Frontend Mapping:


2.2 Toggle Publish/Unpublish Status

When: User clicks the status toggle button on a list row.

API:

GET /destinations/:id/sections/climate
Authorization: Bearer <jwt_token>

Response:

{
  "message": "SUC_SECTION_DETAILS",
  "data": {
    "content": "<p>Mediterranean climate with mild winters and hot summers...</p>",
    "media": []
  }
}

Frontend Mapping:


6.2 Save Climate Section

PUT /destinations/:id/sections/climate
Content-Type: application/json
Authorization: Bearer <jwt_token>

Request Body:

{
  "content": "<p>France has a temperate climate with mild winters and warm summers. The Mediterranean coast enjoys hot, dry summers.</p>"
}

6.3 Upload Image in Climate Editor

POST /destinations/:id/sections/climate/media
Content-Type: multipart/form-data
Authorization: Bearer <jwt_token>

7. Screen 2: Getting There Tab

Identical pattern, with type = getting_there.

7.1 Load Getting There Section

GET /destinations/:id/sections/getting_there
Authorization: Bearer <jwt_token>

Response:

{
  "message": "SUC_SECTION_DETAILS",
  "data": {
    "content": "<p>France is well-connected by air, rail, and road. Major airports include Charles de Gaulle (Paris) and Nice Cote d'Azur.</p>",
    "media": []
  }
}

Frontend Mapping:


7.2 Save Getting There Section

PUT /destinations/:id/sections/getting_there
Content-Type: application/json
Authorization: Bearer <jwt_token>

Request Body:

{
  "content": "<p>France is well-connected by air, rail, and road. Major airports include Charles de Gaulle (Paris) and Nice Cote d'Azur.</p>"
}

7.3 Upload Image in Getting There Editor

POST /destinations/:id/sections/getting_there/media
Content-Type: multipart/form-data
Authorization: Bearer <jwt_token>

8. Screen 2: Docs & Health Tab

Identical pattern, with type = docs_health.

8.1 Load Docs & Health Section

GET /destinations/:id/sections/docs_health
Authorization: Bearer <jwt_token>

Response:

{
  "message": "SUC_SECTION_DETAILS",
  "data": {
    "content": "<p>No visa required for EU citizens. Schengen visa for non-EU visitors.</p>",
    "media": []
  }
}

Frontend Mapping:


8.2 Save Docs & Health Section

PUT /destinations/:id/sections/docs_health
Content-Type: application/json
Authorization: Bearer <jwt_token>

Request Body:

{
  "content": "<p>Valid passport required. No visa for EU citizens. Vaccination certificate may be required for certain regions.</p>"
}

8.3 Upload Image in Docs & Health Editor

POST /destinations/:id/sections/docs_health/media
Content-Type: multipart/form-data
Authorization: Bearer <jwt_token>

9. Common Patterns

9.1 Load Full Destination (Edit Mode)

When opening an existing destination for editing, the frontend can either:

Option A — Lazy Load (Recommended):

Option B — Eager Load:

GET /destinations/:id

Response:

{
  "message": "SUC_DESTINATION_FULL",
  "data": {
    "id": 2,
    "name": "France",
    "type": "country",
    "parentId": 1,
    "heroImage": "https://cdn.example.com/france-hero.jpg",
    "description": "A beautiful country...",
    "status": "published",
    "overview": [
      { "id": 1, "key": "Currency", "value": "Euro (EUR)", "order": 1, "image": null },
      { "id": 2, "key": "Language", "value": "French", "order": 2, "image": null },
      { "id": 3, "key": "Best Time to Travel", "value": "April to June", "order": 3, "image": "https://cdn.example.com/icons/calendar.png" }
    ],
    "sections": {
      "highlights": {
        "content": "<p>France is famous for...</p>",
        "media": [
          { "id": 10, "fileName": "eiffel-tower.jpg", "link": "https://cdn.example.com/..." }
        ]
      },
      "climate": null,
      "getting_there": null,
      "docs_health": null
    }
  }
}

Note: The sections object keys are highlights, climate, getting_there, docs_health. Values are either { content, media } objects or null if the section has not been created yet.


9.2 Tab Switching Strategy

Tab APIs Called On Switch
Hero & Intro Already loaded from GET /destinations/:id or base state
Overview Facts GET /destinations/:id/overview
Highlights GET /destinations/:id/sections/highlights + GET /destinations/:id/sections/highlights/media
Climate GET /destinations/:id/sections/climate + GET /destinations/:id/sections/climate/media
Getting There GET /destinations/:id/sections/getting_there + GET /destinations/:id/sections/getting_there/media
Docs & Health GET /destinations/:id/sections/docs_health + GET /destinations/:id/sections/docs_health/media

Optimization: Cache section data in component state so revisiting a tab doesn't refetch.


9.3 Authentication Header

All write endpoints (POST, PUT, PATCH, DELETE) require:

Authorization: Bearer <jwt_access_token>

The user must have Permission.DESTINATION = 18 assigned to their role.


9.4 Image Upload Flow in Rich Text Editor

User clicks "Insert Image" in editor
        |
        v
File picker opens
        |
        v
User selects image file
        |
        v
POST /destinations/:id/sections/:type/media
        |
        v
Backend uploads to S3, returns { link }
        |
        v
Frontend inserts <img src="{link}" /> into editor HTML
        |
        v
User clicks "Save" tab
        |
        v
PUT /destinations/:id/sections/:type
Body: { "content": "<p>...<img src=\"{link}\">...</p>" }
        |
        v
Section saved with embedded image URLs

10. Error Handling

10.1 Common Error Codes

Error Code HTTP Status When It Happens Frontend Action
ERR_INVALID_PARENT_WORLD_AREA 400 world_area has a parentId Show inline error on parent dropdown
ERR_INVALID_PARENT_COUNTRY 400 country parent is not a world_area Show inline error on parent dropdown
ERR_INVALID_PARENT_REGION 400 region parent is not a country Show inline error on parent dropdown
ERR_DESTINATION_NAME_EXISTS 400 Name already used (case-insensitive) Show inline error on name field
ERR_DESTINATION_NOT_FOUND 404 ID does not exist or is soft-deleted Redirect to list page with error toast
ERR_INVALID_SECTION_TYPE 400 Section type is not one of the 4 allowed Internal bug — should not happen
ERR_MEDIA_FILE_REQUIRED 400 Image upload called without a file Show "Please select a file" message

10.2 Generic Error Response Shape

{
  "statusCode": 400,
  "message": "ERR_INVALID_PARENT_COUNTRY"
}

10.3 Frontend Error Handling Checklist


11. i18n Message Reference

All API responses use message codes that map to human-readable strings in src/i18n/en/success.json and src/i18n/en/error.json.

Success Messages

Code Message
SUC_DESTINATION_CREATED Destination created successfully.
SUC_DESTINATION_LIST Destination list fetched successfully.
SUC_DESTINATION_DETAILS Destination details fetched successfully.
SUC_DESTINATION_UPDATED Destination updated successfully.
SUC_DESTINATION_DELETED Destination deleted successfully.
SUC_DESTINATION_STATUS_UPDATED Destination status updated successfully.
SUC_PARENT_OPTIONS Parent options fetched successfully.
SUC_DESTINATION_FULL Destination details with overview and sections fetched successfully.
SUC_OVERVIEW_UPDATED Overview facts updated successfully.
SUC_OVERVIEW_DETAILS Overview facts fetched successfully.
SUC_SECTION_UPDATED Section content updated successfully.
SUC_SECTION_DETAILS Section details fetched successfully.
SUC_SECTION_MEDIA_UPLOADED Section media uploaded successfully.
SUC_SECTION_MEDIA_LIST Section media list fetched successfully.
SUC_SECTION_MEDIA_DELETED Section media deleted successfully.

Error Messages

Code Message
ERR_DESTINATION_NOT_FOUND Destination not found.
ERR_INVALID_PARENT_WORLD_AREA Invalid parent. World areas cannot have a parent, and countries must reference a valid world area.
ERR_INVALID_PARENT_COUNTRY Invalid parent. Regions must reference a valid country.
ERR_DESTINATION_NAME_EXISTS A destination with this name already exists.
ERR_INVALID_SECTION_TYPE Invalid section type. Must be one of: highlights, climate, getting_there, docs_health.
ERR_MEDIA_FILE_REQUIRED Media file is required for upload.
ERR_INVALID_TYPE Invalid destination type.

Appendix A: Full API Summary

Public Endpoints (No Auth Required)

Method Endpoint Description
GET /destinations List destinations with filters
GET /destinations/parent-options?type= Get parent dropdown options
GET /destinations/:id Get full destination with overview and sections
GET /destinations/:id/overview Get overview facts
GET /destinations/:id/sections/:type Get section by type
GET /destinations/:id/sections/:type/media List section media

Authenticated Endpoints (JWT + DESTINATION Permission)

Method Endpoint Description
POST /destinations Create destination
PATCH /destinations/:id Update destination base fields
PATCH /destinations/:id/status Toggle publish/unpublish
DELETE /destinations/:id Soft delete destination
PUT /destinations/:id/overview Replace all overview facts
PUT /destinations/:id/sections/:type Upsert section content
POST /destinations/:id/sections/:type/media Upload section media
DELETE /destinations/:id/sections/:type/media/:mediaId Delete section media

Valid Section Types

:type can only be one of:


End of Frontend Integration Guide