# 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: ```json { "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 } ] } } ``` - `pollId` — the active poll ID (null if no active poll) - `poll` — full poll object (null if no active poll) - `hasVoted` — whether the current user has voted - `userVotedOptionId` — which option the user picked (null if not voted) ### Result visibility - If `showResults` is `true` — vote counts are always visible - If `showResults` is `false` and user has **not** voted — `totalVotes` and all `voteCount` values are `0` - If `showResults` is `false` and user **has** voted — vote counts are visible ### 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:** ```json { "optionId": "xYzAbCdEfG" } ``` **Success response (200):** Returns the updated `PollDto` with the user's vote applied. ```json { "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 - A user can only vote **once** per poll - Votes can only be submitted when `status` is `Active` (2) and current time is between `startsAt` and `endsAt` - After voting, the response includes updated vote counts --- ## 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: - `id` — needed for the vote request - `text` — the option label - `voteCount` — number of votes (may be 0 if results are hidden) ### 3. Handle vote state Check `poll.hasVoted`: - **Not voted:** Show voting buttons, allow user to select an option and submit - **Already voted:** Highlight the user's choice using `poll.userVotedOptionId`, disable voting buttons ### 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": "" } ``` 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 ``` ```json { "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 } ] } ``` - Minimum 2 options required - Poll is created in `Draft` status - `showResults` — whether users can see vote counts before voting - `showOnEmbed` — whether poll appears on the video embed ### Update poll ``` PUT /admin/v1/polls/{id} ``` Same body as create, plus: - `id` — poll ID (in body) - `options[].id` — include the existing option ID to update it; omit to add new; missing IDs get deleted ```json { "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 ``` - Changes status from Draft to Active - Fails if the video already has another active poll - Fails if poll has fewer than 2 options ### 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: ```json { "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