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.
| 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 |
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:
data.destinationsstatusWhen: 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:
content into the climate editormedia array contains uploaded climate-related images (e.g., weather charts)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>"
}
POST /destinations/:id/sections/climate/media
Content-Type: multipart/form-data
Authorization: Bearer <jwt_token>
Identical pattern, with type = getting_there.
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:
content into the editormedia array contains uploaded transport-related images (e.g., airport photos, route maps)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>"
}
POST /destinations/:id/sections/getting_there/media
Content-Type: multipart/form-data
Authorization: Bearer <jwt_token>
Identical pattern, with type = docs_health.
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:
content into the editormedia array contains uploaded document/health-related images (e.g., visa samples, vaccination cards)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>"
}
POST /destinations/:id/sections/docs_health/media
Content-Type: multipart/form-data
Authorization: Bearer <jwt_token>
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.
| 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.
All write endpoints (POST, PUT, PATCH, DELETE) require:
Authorization: Bearer <jwt_access_token>
The user must have Permission.DESTINATION = 18 assigned to their role.
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
| 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 |
{
"statusCode": 400,
"message": "ERR_INVALID_PARENT_COUNTRY"
}
All API responses use message codes that map to human-readable strings in src/i18n/en/success.json and src/i18n/en/error.json.
| 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. |
| 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. |
| 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 |
| 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 |
:type can only be one of:
highlightsclimategetting_theredocs_healthEnd of Frontend Integration Guide