md-platform

polls-frontend-guide.md
View raw Back to list

Polls Feature - Frontend Integration Guide

Overview

Polls are attached to videos. A video can have one active poll at a time. Polls are managed by admins and displayed to users inside the video player/page.


How polls appear in the video response

When you fetch a video (details endpoint), the response includes:

{
  "id": "KxLr9mN2oP",
  "name": "My Video",
  "pollId": "aBcDeFgHiJ",
  "poll": {
    "id": "aBcDeFgHiJ",
    "title": "Who will win?",
    "image": "https://cdn.example.com/poll-image.jpg",
    "startsAt": "2026-05-08T10:00:00Z",
    "endsAt": "2026-05-10T10:00:00Z",
    "status": 2,
    "showResults": true,
    "videoId": "KxLr9mN2oP",
    "totalVotes": 150,
    "hasVoted": true,
    "userVotedOptionId": "xYzAbCdEfG",
    "options": [
      { "id": "xYzAbCdEfG", "text": "Team A", "displayOrder": 1, "voteCount": 90 },
      { "id": "hIjKlMnOpQ", "text": "Team B", "displayOrder": 2, "voteCount": 60 }
    ]
  }
}

Result visibility

Poll status values

Value Name Description
1 Draft Not visible to users
2 Active Visible and accepting votes
3 Ended Closed, no more votes

Only active polls (status: 2) appear in the video response.


Client/Mobile Endpoints

Submit a vote

POST /client/v1/polls/{pollId}/vote
POST /mobile/v1/polls/{pollId}/vote

Headers: pictv-token, pictv-user (authenticated)

Request body:

{
  "optionId": "xYzAbCdEfG"
}

Success response (200): Returns the updated PollDto with the user's vote applied.

{
  "id": "aBcDeFgHiJ",
  "title": "Who will win?",
  "image": "https://cdn.example.com/poll-image.jpg",
  "startsAt": "2026-05-08T10:00:00Z",
  "endsAt": "2026-05-10T10:00:00Z",
  "status": 2,
  "showResults": true,
  "videoId": "KxLr9mN2oP",
  "totalVotes": 151,
  "hasVoted": true,
  "userVotedOptionId": "xYzAbCdEfG",
  "options": [
    { "id": "xYzAbCdEfG", "text": "Team A", "displayOrder": 1, "voteCount": 91 },
    { "id": "hIjKlMnOpQ", "text": "Team B", "displayOrder": 2, "voteCount": 60 }
  ]
}

Error responses:

Status Reason
400 Already voted on this poll
400 Poll is not within its active time range
404 Poll not found or not active
404 Option not found

Important rules


Frontend Implementation Guide

1. Display the poll

When loading a video, check if poll is not null in the video response:

if (video.poll) {
  // Show poll UI
}

2. Show options

Render the options sorted by displayOrder. Each option has:

3. Handle vote state

Check poll.hasVoted:

4. Show/hide results

const showResults = poll.showResults || poll.hasVoted;

If showResults is false and user hasn't voted, all counts are 0 — just show the options without percentages/bars.

5. Submit vote

On option click:

POST /client/v1/polls/{poll.id}/vote
Body: { "optionId": "<selected option id>" }

On success, replace the poll state with the response data. The response already has hasVoted: true and updated counts.

6. Calculate percentages

const percentage = poll.totalVotes > 0
  ? Math.round((option.voteCount / poll.totalVotes) * 100)
  : 0;

7. Poll image

poll.image is optional. If present, display it as a header/banner for the poll.

8. Time validation

The API enforces time validation server-side, but for UX you can hide the vote button if:

const now = new Date();
const isWithinRange = now >= new Date(poll.startsAt) && now <= new Date(poll.endsAt);
const canVote = poll.status === 2 && isWithinRange && !poll.hasVoted;

Admin Endpoints

Base path: /admin/v1/polls

Method Path Description
POST / Create a poll
GET / List polls (query params: q, status, page, pageSize)
GET /{id} Get poll details
PUT /{id} Update a poll
POST /{id}/activate Activate a poll (Draft -> Active)
POST /{id}/end End a poll (Active -> Ended)
DELETE /{id} Delete a poll

Create poll

POST /admin/v1/polls
{
  "videoId": "KxLr9mN2oP",
  "title": "Who will win?",
  "image": "https://cdn.example.com/poll-image.jpg",
  "startsAt": "2026-05-08T10:00:00Z",
  "endsAt": "2026-05-10T10:00:00Z",
  "showResults": true,
  "showOnEmbed": true,
  "options": [
    { "text": "Team A", "displayOrder": 1 },
    { "text": "Team B", "displayOrder": 2 }
  ]
}

Update poll

PUT /admin/v1/polls/{id}

Same body as create, plus:

{
  "id": "aBcDeFgHiJ",
  "videoId": "KxLr9mN2oP",
  "title": "Updated title",
  "image": null,
  "startsAt": "2026-05-08T10:00:00Z",
  "endsAt": "2026-05-12T10:00:00Z",
  "showResults": false,
  "showOnEmbed": true,
  "options": [
    { "id": "xYzAbCdEfG", "text": "Team A (updated)", "displayOrder": 1 },
    { "text": "Team C (new)", "displayOrder": 3 }
  ]
}

Activate poll

POST /admin/v1/polls/{id}/activate

End poll

POST /admin/v1/polls/{id}/end

Changes status to Ended. No more votes accepted.

List polls

GET /admin/v1/polls?q=search&status=2&page=1&pageSize=10

Status filter values: 1 (Draft), 2 (Active), 3 (Ended)

Admin poll response

Includes video details and audit fields:

{
  "id": "aBcDeFgHiJ",
  "title": "Who will win?",
  "image": "https://cdn.example.com/poll-image.jpg",
  "startsAt": "2026-05-08T10:00:00Z",
  "endsAt": "2026-05-10T10:00:00Z",
  "status": 2,
  "showResults": true,
  "showOnEmbed": true,
  "videoId": "KxLr9mN2oP",
  "video": {
    "id": "KxLr9mN2oP",
    "name": "My Video",
    "thumbnail": "https://cdn.example.com/thumb.jpg"
  },
  "totalVotes": 150,
  "createdAt": "2026-05-07T12:00:00Z",
  "updatedAt": "2026-05-08T09:00:00Z",
  "createdBy": "admin-user-id",
  "updatedBy": "admin-user-id",
  "options": [
    { "id": "xYzAbCdEfG", "text": "Team A", "displayOrder": 1, "voteCount": 90 },
    { "id": "hIjKlMnOpQ", "text": "Team B", "displayOrder": 2, "voteCount": 60 }
  ]
}

Admin Poll Lifecycle

Draft  -->  Active  -->  Ended
         (activate)    (end)
  1. Create poll (status: Draft)
  2. Configure options, title, image, dates
  3. Activate when ready (only one active poll per video)
  4. End when finished