---
feature: Static pages, support requests, FAQs and sliders
slug: content-support
surface: admin
consumer: vue
version: 0.1.0
status: in-progress
brd_version: "Morvia Project Guide"
brd_ids: [ADM-045, ADM-046]
business_rules: []
decisions: [TD-11, TD-14]
schema_tables: [static_pages, static_page_translations, f_a_q_s, f_a_q_translations, sliders, slider_translations, contact_messages, users, media]
screens: []
related_docs: []
last_updated: 2026-11-12
---

# Content and support — Vue Dashboard Integration Guide

> Base URL: `{HOST}/api/v1/admin` · [Shared conventions](../API_CONVENTIONS.md). This documents existing base code, not completed BRD acceptance.

## Quick reference

| | |
|---|---|
| Version | 0.1.0 (2026-11-12); dashboard action: integrate only after resolving §13 limitations. |
| Status | in-progress; ADM-045, ADM-046; TD-11, TD-14. |
| Auth | Bearer JWT, active account and role, `rbac.auto`; route name is permission key. |

| Method | Path | Route / permission | Purpose |
|---|---|---|---|
| GET | `/pages` | `admin.pages.index` | List pages |
| POST | `/pages` | `admin.pages.store` | Create page |
| GET | `/pages/{page}` | `admin.pages.show` | Page details |
| PUT/PATCH | `/pages/{page}` | `admin.pages.update` | Update page |
| DELETE | `/pages/{page}` | `admin.pages.destroy` | Delete page |
| GET | `/faqs` | `admin.faqs.index` | List FAQs |
| POST | `/faqs` | `admin.faqs.store` | Create FAQ |
| GET | `/faqs/{faq}` | `admin.faqs.show` | FAQ details |
| PUT/PATCH | `/faqs/{faq}` | `admin.faqs.update` | Update FAQ |
| DELETE | `/faqs/{faq}` | `admin.faqs.destroy` | Delete FAQ |
| GET | `/sliders` | `admin.sliders.index` | List sliders |
| POST | `/sliders` | `admin.sliders.store` | Create slider |
| GET | `/sliders/{slider}` | `admin.sliders.show` | Slider details |
| PUT/PATCH | `/sliders/{slider}` | `admin.sliders.update` | Update slider |
| DELETE | `/sliders/{slider}` | `admin.sliders.destroy` | Delete slider |
| GET | `/contact-messages` | `admin.contact-messages.index` | List messages |
| GET | `/contact-messages/{id}` | `admin.contact-messages.show` | Details; marks new as reviewed |
| DELETE | `/contact-messages/{id}` | `admin.contact-messages.destroy` | Delete message |
| POST | `/contact-messages/{id}/reply` | `admin.contact-messages.reply` | Save reply |

**Statuses:** `new`, `reviewed`, `closed`; content uses boolean `is_active`. **Errors:** 401, 403, 404, 422, 500; no feature-specific error code established. **Open decisions:** no specific unresolved DEC identified; see §13 for implementation gaps.

## 1. Business summary

Manage bilingual static pages, FAQs and promotional sliders, and inspect, reply to or delete contact messages. The support workflow and publication controls are incomplete relative to the BRD. Public content is served separately through the shared endpoints in API conventions.

## 2. Pages and permissions

Use the endpoint and permission table above for list, details, create, edit, delete and reply controls. Gate each action separately; hide sidebar entries without the corresponding `index` permission. All route identifiers are public UUID strings (TD-14). Send `Accept: application/json`, `Authorization: Bearer {token}` and `Accept-Language: ar` or `en`; use `Content-Type: application/json` for these forms. Optional `X-Correlation-ID` aids tracing. Local/testing middleware bypasses RBAC checks; it does not establish production permission behavior.

## 3. List / table

All four lists default to pagination (`page`, `per_page`, default 15, clamped 1–100; `limit` is a fallback alias). `paginate=false` or `paginate=0` returns at most 500 rows without pagination metadata. Paginated resource collections use the model table as the item key inside `data`: `static_pages`, `f_a_q_s`, `sliders`, `contact_messages`, plus `links` and `meta`. Unpaginated `data` is the resource collection. Exact serialization needs runtime confirmation (§13).

| List | `search` fields | `filters[field]` | Sort |
|---|---|---|---|
| Pages | `is_active`, `type`, `title`, `content`, `created_at`; translations searched across locales | Same fields accepted by generic pipeline; use `is_active`, `type`, `created_at` only. Translation filters target base columns and may fail. | `sort[field]=asc|desc`, or `sort=field&direction=asc|desc`; use actual base columns `type`, `is_active`, `created_at`. |
| FAQs | `is_active`, `type`, `created_at`, `question`, `answer`; translations searched across locales | Same fields accepted; use `is_active`, `type`, `created_at` only. Translation filters may fail. | Use actual base columns `type`, `is_active`, `created_at`. |
| Sliders | No configured searchable fields; `search` and `filters` have no effect. | None configured. | Use actual base columns `link`, `sort_order`, `is_active`, `created_at`. |
| Messages | `name`, `email`, `phone`, `subject`, `message`, `status` | Same names, exact match, including `filters[status]`. | Same base columns or `created_at`. |

Default sort is newest (`created_at` descending). Sorting has two sequential implementations; the second directly orders by the supplied field without an allowlist. Translated-field sorts can add an invalid base-column order; unknown columns can fail. Do not send translated or arbitrary sort fields. No filters or sort have been runtime verified here.

List fields and their types are listed in §8. Display localized titles/questions, `is_active`, and dates; for messages show name, email, phone, subject, status and reply date. `created_at`, `updated_at` and `replied_at` are nullable `YYYY-MM-DD HH:mm:ss` strings, not ISO 8601.

## 4. Details view

GET a UUID using the relevant `show` route. Page detail adds localized `content`, `updated_at`, and `en`/`ar` objects containing `title` and `content`. FAQ detail adds `updated_at` and `en`/`ar` objects containing `question` and `answer`. Slider detail adds `updated_at` and `en`/`ar` objects containing `title` and `subtitle`. Each translation value may be null. Message detail adds `message`, `reply`, `replied_by` and `updated_at`; `replied_by` is null or `{id, full_name, email}`, with UUID `id`. Details are otherwise the list fields (§8).

Opening a new message changes its stored status to reviewed before returning it. Existing reviewed/closed messages retain their status. No full response examples are included: a real capture was not safely obtained; see §13.

## 5. Forms (create / edit)

POST creates (201); PUT or PATCH updates (200). Update rules become `sometimes` only when the route UUID resolves; an unknown UUID can therefore fail validation before returning 404. Fields below are validated inputs; do not infer extra validation from UI requirements.

| Resource / field | Create | FormRequest validation and notes |
|---|---|---|
| Page `type` | required | One of §7 page values. |
| Page `en.title`, `ar.title` | required | string, max 255 each. |
| Page `en.content`, `ar.content` | required | string each; no maximum in request. |
| FAQ `en.question`, `ar.question` | required | string, max 500 each. |
| FAQ `en.answer`, `ar.answer` | required | string, max 5000 each. |
| FAQ `type` | optional | `sometimes|string`; request does not enforce enum membership, though model casts to FAQType. Send only §7 values. |
| Slider `en.title`, `ar.title` | required | string, max 255 each. |
| Slider `en.subtitle`, `ar.subtitle` | optional | nullable string, max 255 each. |
| Slider `link` | optional | nullable URL, max 500. |
| Slider `sort_order` | optional | nullable integer; no min/max rule. |
| All `is_active` | optional | `sometimes|boolean`. |
| Page/slider `image` | optional | nullable string media **hash**, existing image MIME, physical file present, unattached or attached to this same record. It is not a file or URL. |

All required bilingual fields become `sometimes` on a resolved update; supplied strings must still pass their rules. Omit fields to leave them unchanged. Do not assume `image: null` removes an image: media setter only buffers nonempty hashes. FormRequest does not validate image dimensions, extension or image byte size; model metadata advertises page 1200×630 / 3072 KB and slider 1920×800 / 5120 KB, jpg/jpeg/png/webp, but enforcement is not established at these endpoints.

Upload is a separate `POST /api/v1/client/upload` (`client.upload`), multipart `file` required, max 10240 KB and allowed jpg/jpeg/png/gif/webp/pdf/avi/mov/wmv/flv/mkv/mp4/mp3/wav; optional `type` is `image`, `document`, `video`, or `audio`. Use `type=image` and pass the returned media hash as `image`. The source declares upload response fields `id` (numeric internal ID), `hash`, `url`, `mime_type`, `size`, `original_name`, and optional `variants`. The numeric upload ID conflicts with TD-14; use `hash` for attachment. This is a client route, not an admin upload route; verify access and response contract before wiring the dashboard. Image hashes are not exposed in the content resources: `image` on responses is a URL or placeholder.

Create/update returns the corresponding detail resource plus the locale objects, in the standard envelope. Delete returns 200 with null data. These are source-derived, not captured examples.

## 6. Actions (non-CRUD)

### 6.1 Reply

`POST /contact-messages/{id}/reply` · `admin.contact-messages.reply` · `reply` required string, max 5000. On success returns HTTP 200, `data: null`; stores reply, authenticated replier and current time. A later reply overwrites the previous one. It does not change status and the service does not send mail, push or another notification. GET details again to refresh the fields.

| HTTP | `error_code` | When / dashboard action |
|---|---|---|
| 401 | not established | Missing or invalid session; return to login. |
| 403 | not established | Inactive account/role or permission denied; hide action and show message. |
| 404 | not established | UUID absent; refresh list. |
| 422 | not established | Form validation or invalid media; display first `message`. |
| 500 | not established | Server, database, invalid sort or enum failure; show generic error and report it. |

These shared cases apply to CRUD as relevant. Validation normally returns the first error message with null `data`; do not depend on a field error map. No endpoint-specific typed error codes were found. A failed unknown update may return 422 before 404.

## 7. Statuses and enums

| Field | Accepted value | Label / behavior |
|---|---|---|
| Page type | `terms`, `privacy`, `about`, `about_us`, `privacy_policy`, `warranty_policy`, `return_policy`, `shipping_policy` | `Terms`, `Privacy`, `About`, `About Us`, `Privacy Policy`, `Warranty Policy`, `Return Policy`, `Shipping Policy` respectively. |
| FAQ type | `general`, `orders_and_payment`, `shipping_and_delivery`, `returns_and_exchanges`, `products`, `account_and_support` | `General`, `Orders And Payment`, `Shipping And Delivery`, `Returns And Exchanges`, `Products`, `Account And Support` respectively. |
| Message status | `new`, `reviewed`, `closed` | `{value,label}`; English `New`, `Reviewed`, `Closed`; Arabic `جديد`, `تمت المراجعة`, `مغلق`. New becomes reviewed on show. No explicit close/reopen endpoint. |
| Content `is_active` | boolean | Active/inactive; no draft or publication enum. |

Page/FAQ `type` **responses are label strings or null**, not `{value,label}`; labels are generated in English regardless of locale. Map labels to request values using the table, with fallback for unknown strings. Message `status` is the `{value,label}` object. No `can_*` flags are returned; permissions govern available controls, not status flags.

## Data model

| Table | Access | Notes |
|---|---|---|
| `static_pages`, `static_page_translations` | read/write/delete | Bilingual page and active flag. |
| `f_a_q_s`, `f_a_q_translations` | read/write/delete | Bilingual question/answer. |
| `sliders`, `slider_translations` | read/write/delete | Bilingual title/subtitle, link and order. |
| `contact_messages` | read/write/delete | Status, reply and internal note columns; note not exposed. |
| `users` | read | Replier summary; submitter is not exposed. |
| `media` | read/write | Hash attachment and URL resolution. |

## 8. TypeScript types

```ts
type UUID = string;
type DateTime = string; // YYYY-MM-DD HH:mm:ss, server timezone
interface Envelope<T> { status: 'success' | 'fail' | 'error'; message: string; data: T }
interface EnumValue<V extends string> { value: V; label: string }
type PageType = 'terms' | 'privacy' | 'about' | 'about_us' | 'privacy_policy' | 'warranty_policy' | 'return_policy' | 'shipping_policy';
type FAQType = 'general' | 'orders_and_payment' | 'shipping_and_delivery' | 'returns_and_exchanges' | 'products' | 'account_and_support';
type SupportStatus = 'new' | 'reviewed' | 'closed';
type LocalePair<T> = { en: T; ar: T };
interface Page { id: UUID; type: string | null; title: string | null; image: string | null; is_active: boolean; created_at: DateTime | null }
interface PageDetail extends Page, LocalePair<{title: string | null; content: string | null}> { content: string | null; updated_at: DateTime | null }
interface FAQ { id: UUID; type: string | null; question: string | null; answer: string | null; is_active: boolean; created_at: DateTime | null }
interface FAQDetail extends FAQ, LocalePair<{question: string | null; answer: string | null}> { updated_at: DateTime | null }
interface Slider { id: UUID; title: string | null; subtitle: string | null; link: string | null; image: string | null; sort_order: number | null; is_active: boolean; created_at: DateTime | null }
interface SliderDetail extends Slider, LocalePair<{title: string | null; subtitle: string | null}> { updated_at: DateTime | null }
interface ContactMessage { id: UUID; name: string; email: string; phone: string | null; subject: string; status: EnumValue<SupportStatus>; replied_at: DateTime | null; created_at: DateTime | null }
interface ContactMessageDetail extends ContactMessage { message: string; reply: string | null; replied_by: { id: UUID; full_name: string | null; email: string | null } | null; updated_at: DateTime | null }
interface PageForm { type: PageType; en: {title: string; content: string}; ar: {title: string; content: string}; is_active?: boolean; image?: string | null }
interface FAQForm { type?: FAQType; en: {question: string; answer: string}; ar: {question: string; answer: string}; is_active?: boolean }
interface SliderForm { en: {title: string; subtitle?: string | null}; ar: {title: string; subtitle?: string | null}; link?: string | null; sort_order?: number | null; is_active?: boolean; image?: string | null }
type Update<T> = { [K in keyof T]?: T[K] extends object ? Partial<T[K]> : T[K] };
interface ListQuery { page?: number; per_page?: number; limit?: number; paginate?: 'false' | '0'; search?: string; filters?: Record<string, string | number | boolean>; sort?: Record<string, 'asc' | 'desc'> | string; direction?: 'asc' | 'desc' }
interface PageMeta { current_page: number; from: number | null; last_page: number; path: string; per_page: number; to: number | null; total: number }
type ListData<K extends string, T> = Record<K, T[]> & { links: {first: string | null; last: string | null; prev: string | null; next: string | null}; meta: PageMeta };
```

Types reflect resource declarations and may need refinement after real captures. For an unpaginated list handle a collection separately; do not assume a paginated wrapper.

## 9. Business rules enforced by the backend

| Rule | Backend | Dashboard |
|---|---|---|
| TD-14 | Resolve admin route IDs by UUID; resources expose UUID IDs. | Preserve UUIDs as strings. |
| ADM-046 | Showing new support message marks it reviewed. | Refresh the list after opening. |
| Base validation | Required translations on create; request rules in §5. | Validate before submit and show server errors. |
| Base media | Only existing available image hash accepted. | Upload first; handle attachment failure. |

## 10. Notifications triggered

N/A: no notification dispatch is implemented by these services. A saved reply is not evidence of delivery.

## 11. Integration checklist

- [ ] Gate all screens and actions by the exact route permissions in Quick reference.
- [ ] Send bearer JWT, JSON headers and locale; use UUID route IDs.
- [ ] Implement list params and the source-derived pagination keys in §3; verify runtime shape.
- [ ] Implement both locales and request rules; use the separate media upload flow after confirming access.
- [ ] Handle scalar type labels separately from support status objects; refresh after opening or replying.
- [ ] Handle 401/403/404/422/500, RTL and nullable fields; verify all contracts in a safe isolated environment.
- [ ] Do not expose unsupported publishing, status editing or notes as working controls.

## 12. Acceptance scenarios

| AC ID | Scenario | Expected from existing source |
|---|---|---|
| N/A (ADM-045) | Create/edit bilingual page and list/show it. | Request validates both locales on create; CRUD saves and returns the detail resource. Publication workflow remains incomplete. |
| N/A (base) | Create/edit/delete FAQ or slider. | CRUD routes and validation exist; verify actual persistence, media and response in isolation. |
| N/A (ADM-046) | Open a new support message. | Status becomes reviewed before details are returned. |
| N/A (ADM-046) | Save a reply and inspect message. | Reply, author and timestamp persist; no delivery or status change is implemented. |
| N/A (ADM-046) | Close request or add internal note. | Not supported by admin endpoints. |

These are source-derived scenarios, not test results.

## 13. Open decisions and known limitations

| ID | Impact | Current behavior |
|---|---|---|
| ADM-045 / sprint | Static-page draft/save/publish | Only `is_active` exists; no separate draft, publish action or draft-versus-published versions. No basic support page type is explicitly defined. |
| ADM-046 / sprint | User, status change and internal note | Message resource omits submitting user and internal note; no status or note update route. `closed` exists but cannot be set through these admin routes. |
| Sprint home slider | Home slider CRUD | CRUD routes exist, but publication scheduling/targeting and a verified admin upload flow are not established; search/filter are not configured. |
| TD-11 / TD-14 | Translations and public IDs | Slider text uses translations and content resources expose UUIDs; the separate client upload payload exposes a numeric internal media `id`. End-to-end responses have not been captured here. |
| Base | Type contract | Page/FAQ type emits a label string, not the standard enum object; FAQ request permits arbitrary strings despite enum casting. |
| Base | Lists | Translated filters/sorts can cause SQL errors; sort is not safely restricted. Default pagination and wrapper are source-derived. |
| Base | Reply and media | Reply is stored only; image hash cannot be recovered from resource URL and null removal is not established. |
| Verification | Real examples | No real test response was captured in this docs-only task; all response descriptions and acceptance scenarios above are source-derived, not runtime proof. No fabricated JSON examples are provided. |
| Decisions | Open DEC | No feature-specific open DEC was identified in the brief; gaps above require product/backend resolution rather than an assumed decision. |

## Version history

| Version | Date | Type | Summary | Dashboard action required |
|---|---|---|---|---|
| 0.1.0 | 2026-11-12 | Initial | Document existing content/support routes and gaps. | Verify contracts and account for limitations before integration. |

## Changelog

### 0.1.0 — 2026-11-12

- **Added:** Existing page, FAQ and slider CRUD and support list, show, delete and reply contracts.
- **Changed:** —
- **Fixed:** —
- **Removed / Deprecated:** —
- **Breaking:** No
- **Endpoints affected:** All routes in Quick reference.
- **Dashboard action required:** Verify source-derived contracts; account for §13 gaps.
- **BRD / decisions:** ADM-045, ADM-046, TD-11, TD-14.
