feat(api): add new endpoints for notifications and event FAQs

- Introduced a new PATCH endpoint `/api/v1/notifications/me/read-all` to mark all unread notifications as read for the authenticated user.
- Added a new GET endpoint `/api/v1/events/{eventId}/faqs/page` for retrieving paginated FAQs related to a specific event, with support for pagination, sorting, and filtering.
- Updated the existing GET endpoint `/api/v1/events/{eventId}/guest-list-links/invited-guests/page` to include pagination parameters.
This commit is contained in:
alisaza 2026-09-08 18:54:09 +03:30
parent 5205ad6d16
commit aff6eeace6
3 changed files with 613 additions and 36 deletions

View File

@ -10,6 +10,7 @@ import { TextStyle } from '@tiptap/extension-text-style'
import Highlight from '@tiptap/extension-highlight'
import Image from '@tiptap/extension-image'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { buttonVariants } from '@heroui/react'
import { Dropdown, DropdownItem, DropdownMenu, DropdownTrigger } from '@/components/heroui/Dropdown'
import { addToast } from '@/lib/toast'
@ -20,7 +21,6 @@ import FileUpload from '@/components/media/FileUpload'
import ConsumerModal from '@/components/consumer/ConsumerModal'
import { fileAddress, coerceToString } from '@/helpers'
import { cn } from '@/lib/cn'
import { buttonVariants } from '@heroui/react'
const CONSUMER_TOOLBAR_TRIGGER_CLASS =
'min-h-10 rounded-[10px] border border-consumer-border bg-consumer-surface px-3 text-sm font-medium text-consumer-text outline-none'

View File

@ -137,8 +137,7 @@ function isButtonLikeTrigger(child: ReactElement): boolean {
const displayName =
typeof child.type === 'function' || typeof child.type === 'object'
? (child.type as { displayName?: string; name?: string }).displayName ||
(child.type as { name?: string }).name
? (child.type as { displayName?: string; name?: string }).displayName || (child.type as { name?: string }).name
: undefined
return displayName === 'ConsumerButton' || displayName === 'Button'

View File

@ -2847,6 +2847,74 @@
]
}
},
"/api/v1/notifications/me/read-all": {
"patch": {
"description": "Marks every currently unread in-app notification owned by the authenticated user, including notifications outside loaded pages.",
"operationId": "NotificationsController_markAllAsRead",
"parameters": [],
"responses": {
"200": {
"description": "Number of notifications updated and the shared read timestamp.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationMarkAllReadResponseDto"
}
}
}
},
"400": {
"description": "Validation error - request body, parameters, or query string are invalid",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponseDto"
}
}
}
},
"401": {
"description": "Unauthorized - invalid or missing JWT token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponseDto"
}
}
}
},
"403": {
"description": "Forbidden - Insufficient permissions",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponseDto"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponseDto"
}
}
}
}
},
"security": [
{
"JWT-auth": []
}
],
"summary": "Mark all my in-app notifications as read",
"tags": [
"Notifications"
]
}
},
"/api/v1/notifications/{id}/read": {
"patch": {
"description": "Sets read_at for the current user on the given in-app notification.",
@ -7783,6 +7851,112 @@
]
}
},
"/api/v1/events/{eventId}/faqs/page": {
"get": {
"operationId": "EventFaqsController_listPage",
"parameters": [
{
"name": "eventId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "page",
"required": false,
"in": "query",
"description": "1-based page",
"schema": {
"minimum": 1,
"default": 1,
"type": "number"
}
},
{
"name": "pageSize",
"required": false,
"in": "query",
"description": "Page size (alias accepted: limit)",
"schema": {
"minimum": 1,
"maximum": 100,
"default": 20,
"type": "number"
}
},
{
"name": "sort",
"required": false,
"in": "query",
"description": "Sort field. Prefix with \"-\" for descending (e.g. \"-createdAt\").",
"schema": {
"example": "-createdAt",
"type": "string"
}
},
{
"name": "filters",
"required": false,
"in": "query",
"description": "Column filters echoed back in the response. Values are strings; date ranges use \"from,to\".",
"schema": {
"additionalProperties": {
"type": "string"
},
"type": "object"
}
},
{
"name": "resultType",
"required": false,
"in": "query",
"description": "0 = JSON list (default). 1 = Excel export (returns response.fileData).",
"schema": {
"type": "number",
"enum": [
0,
1
]
}
}
],
"responses": {
"200": {
"description": "Event FAQs ordered by sortOrder and id.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EventFaqResponseDto"
},
"description": "Array of items for the current page."
},
"response": {
"$ref": "#/components/schemas/PaginationMetaDto",
"description": "Pagination metadata for the current query."
}
},
"required": [
"items",
"response"
]
}
}
}
}
},
"summary": "Paginated FAQs for an event",
"tags": [
"Event Extras"
]
}
},
"/api/v1/events/{eventId}/faqs/{id}": {
"patch": {
"operationId": "EventFaqsController_update",
@ -8333,9 +8507,11 @@
"tags": [
"Event Extras"
]
}
},
"/api/v1/events/{eventId}/guest-list-links/invited-guests/page": {
"get": {
"operationId": "EventGuestListLinksController_getInvitedGuests",
"operationId": "EventGuestListLinksController_getInvitedGuestsPage",
"parameters": [
{
"name": "eventId",
@ -8344,18 +8520,90 @@
"schema": {
"type": "string"
}
},
{
"name": "page",
"required": false,
"in": "query",
"description": "1-based page",
"schema": {
"minimum": 1,
"default": 1,
"type": "number"
}
},
{
"name": "pageSize",
"required": false,
"in": "query",
"description": "Page size (alias accepted: limit)",
"schema": {
"minimum": 1,
"maximum": 100,
"default": 20,
"type": "number"
}
},
{
"name": "sort",
"required": false,
"in": "query",
"description": "Sort field. Prefix with \"-\" for descending (e.g. \"-createdAt\").",
"schema": {
"example": "-createdAt",
"type": "string"
}
},
{
"name": "filters",
"required": false,
"in": "query",
"description": "Column filters echoed back in the response. Values are strings; date ranges use \"from,to\".",
"schema": {
"additionalProperties": {
"type": "string"
},
"type": "object"
}
},
{
"name": "resultType",
"required": false,
"in": "query",
"description": "0 = JSON list (default). 1 = Excel export (returns response.fileData).",
"schema": {
"type": "number",
"enum": [
0,
1
]
}
}
],
"responses": {
"200": {
"description": "",
"description": "Paginated list response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/InvitedGuestResponseDto"
},
"description": "Array of items for the current page."
},
"response": {
"$ref": "#/components/schemas/PaginationMetaDto",
"description": "Pagination metadata for the current query."
}
},
"required": [
"items",
"response"
]
}
}
}
@ -8366,7 +8614,7 @@
"bearer": []
}
],
"summary": "Guests selected to invite to this event, for its organizer",
"summary": "Paginated guests selected to invite to this event",
"tags": [
"Event Extras"
]
@ -8409,31 +8657,101 @@
]
}
},
"/api/v1/events/{eventId}/guest-list-links": {
"/api/v1/events/{eventId}/guest-list-links/page": {
"get": {
"description": "Unpaginated, same documented exception as event_faqs/event_media — an organizer linking dozens of guest lists to one event is not a realistic scenario.",
"operationId": "EventGuestListLinksController_list",
"operationId": "EventGuestListLinksController_listPage",
"parameters": [
{
"name": "eventId",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "page",
"required": false,
"in": "query",
"description": "1-based page",
"schema": {
"minimum": 1,
"default": 1,
"type": "number"
}
},
{
"name": "pageSize",
"required": false,
"in": "query",
"description": "Page size (alias accepted: limit)",
"schema": {
"minimum": 1,
"maximum": 100,
"default": 20,
"type": "number"
}
},
{
"name": "sort",
"required": false,
"in": "query",
"description": "Sort field. Prefix with \"-\" for descending (e.g. \"-createdAt\").",
"schema": {
"example": "-createdAt",
"type": "string"
}
},
{
"name": "filters",
"required": false,
"in": "query",
"description": "Column filters echoed back in the response. Values are strings; date ranges use \"from,to\".",
"schema": {
"additionalProperties": {
"type": "string"
},
"type": "object"
}
},
{
"name": "resultType",
"required": false,
"in": "query",
"description": "0 = JSON list (default). 1 = Excel export (returns response.fileData).",
"schema": {
"type": "number",
"enum": [
0,
1
]
}
}
],
"responses": {
"200": {
"description": "",
"description": "Paginated list response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/GuestListLinkResponseDto"
},
"description": "Array of items for the current page."
},
"response": {
"$ref": "#/components/schemas/PaginationMetaDto",
"description": "Pagination metadata for the current query."
}
},
"required": [
"items",
"response"
]
}
}
}
@ -8444,11 +8762,13 @@
"bearer": []
}
],
"summary": "Guest lists linked to this event (unpaginated)",
"summary": "Paginated guest lists linked to this event",
"tags": [
"Event Extras"
]
}
},
"/api/v1/events/{eventId}/guest-list-links": {
"post": {
"operationId": "EventGuestListLinksController_link",
"parameters": [
@ -16426,7 +16746,7 @@
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SupportTicketResponseDto"
"$ref": "#/components/schemas/SupportTicketSummaryResponseDto"
},
"description": "Array of items for the current page."
},
@ -16455,6 +16775,82 @@
]
}
},
"/api/v1/support-tickets/{id}/messages": {
"get": {
"description": "Returns the latest messages first by cursor selection, while items inside each response are ordered oldest-to-newest. Pass `olderCursor` as `before` to load older history.",
"operationId": "SupportTicketsController_listMessagesMine",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "pageSize",
"required": false,
"in": "query",
"description": "Number of messages to return.",
"schema": {
"minimum": 1,
"maximum": 50,
"default": 20,
"type": "number"
}
},
{
"name": "before",
"required": false,
"in": "query",
"description": "Load messages strictly older than this message id. The cursor must belong to this ticket.",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Cursor-paginated support ticket messages.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SupportTicketMessageResponseDto"
},
"description": "Array of items for the current page."
},
"response": {
"$ref": "#/components/schemas/PaginationMetaDto",
"description": "Pagination metadata for the current query."
}
},
"required": [
"items",
"response"
]
}
}
}
}
},
"security": [
{
"JWT-auth": []
}
],
"summary": "List my support ticket messages",
"tags": [
"Support Tickets"
]
}
},
"/api/v1/support-tickets/{id}": {
"get": {
"operationId": "SupportTicketsController_getMine",
@ -16474,7 +16870,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SupportTicketResponseDto"
"$ref": "#/components/schemas/SupportTicketSummaryResponseDto"
}
}
}
@ -16485,7 +16881,7 @@
"JWT-auth": []
}
],
"summary": "Get my support ticket thread",
"summary": "Get my support ticket metadata",
"tags": [
"Support Tickets"
]
@ -16520,7 +16916,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SupportTicketResponseDto"
"$ref": "#/components/schemas/SupportTicketSummaryResponseDto"
}
}
}
@ -16611,7 +17007,7 @@
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SupportTicketResponseDto"
"$ref": "#/components/schemas/SupportTicketSummaryResponseDto"
},
"description": "Array of items for the current page."
},
@ -18860,10 +19256,68 @@
]
}
},
"/api/v1/blog-articles": {
"/api/v1/blog-articles/page": {
"get": {
"operationId": "BlogArticlesController_list",
"operationId": "BlogArticlesController_listPage",
"parameters": [
{
"name": "page",
"required": false,
"in": "query",
"description": "1-based page",
"schema": {
"minimum": 1,
"default": 1,
"type": "number"
}
},
{
"name": "pageSize",
"required": false,
"in": "query",
"description": "Page size (alias accepted: limit)",
"schema": {
"minimum": 1,
"maximum": 100,
"default": 20,
"type": "number"
}
},
{
"name": "sort",
"required": false,
"in": "query",
"description": "Sort field. Prefix with \"-\" for descending (e.g. \"-createdAt\").",
"schema": {
"example": "-createdAt",
"type": "string"
}
},
{
"name": "filters",
"required": false,
"in": "query",
"description": "Column filters echoed back in the response. Values are strings; date ranges use \"from,to\".",
"schema": {
"additionalProperties": {
"type": "string"
},
"type": "object"
}
},
{
"name": "resultType",
"required": false,
"in": "query",
"description": "0 = JSON list (default). 1 = Excel export (returns response.fileData).",
"schema": {
"type": "number",
"enum": [
0,
1
]
}
},
{
"name": "categorySlug",
"required": false,
@ -18893,26 +19347,44 @@
"required": false,
"in": "query",
"schema": {
"type": "boolean"
"type": "string",
"enum": [
"true",
"false"
]
}
}
],
"responses": {
"200": {
"description": "",
"description": "Published blog articles with deterministic pagination.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/BlogArticleSummaryResponseDto"
},
"description": "Array of items for the current page."
},
"response": {
"$ref": "#/components/schemas/PaginationMetaDto",
"description": "Pagination metadata for the current query."
}
},
"required": [
"items",
"response"
]
}
}
}
}
},
"summary": "Published blog articles (unpaginated, for hub/sitemap pages)",
"summary": "Paginated published blog articles",
"tags": [
"Blog Articles"
]
@ -19494,6 +19966,8 @@
"CAPACITY_BELOW_BOOKED",
"IDENTITY_REQUIRED",
"HOST_EVENT_LIMIT_REACHED",
"EVENT_FAQ_LIMIT_REACHED",
"BANK_ACCOUNT_LIMIT_REACHED",
"NOT_CHECKED_IN",
"BOOKING_NOT_CONFIRMED",
"REVIEW_LOCKED",
@ -21405,6 +21879,23 @@
"totalUnread"
]
},
"NotificationMarkAllReadResponseDto": {
"type": "object",
"properties": {
"updatedCount": {
"type": "number",
"example": 12
},
"readAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"updatedCount",
"readAt"
]
},
"PushVapidPublicKeyResponseDto": {
"type": "object",
"properties": {
@ -23035,6 +23526,7 @@
}
},
"faqs": {
"maxItems": 20,
"type": "array",
"items": {
"$ref": "#/components/schemas/CreateEventFaqDto"
@ -24581,6 +25073,9 @@
"$ref": "#/components/schemas/EventFaqResponseDto"
}
},
"faqsTotal": {
"type": "number"
},
"organizer": {
"$ref": "#/components/schemas/EventLandingOrganizerDto"
},
@ -24600,6 +25095,7 @@
"city",
"media",
"faqs",
"faqsTotal",
"organizer",
"reviews",
"reviewsTotal"
@ -29841,6 +30337,88 @@
"messages"
]
},
"SupportTicketSummaryResponseDto": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"userId": {
"type": "string",
"format": "uuid"
},
"subject": {
"type": "string"
},
"category": {
"type": "string",
"enum": [
"general",
"account",
"event",
"booking",
"payment",
"technical"
]
},
"priority": {
"type": "string",
"enum": [
"low",
"normal",
"high",
"urgent"
]
},
"status": {
"type": "string",
"enum": [
"open",
"waiting_admin",
"waiting_user",
"closed"
]
},
"lastMessageAt": {
"type": "string",
"format": "date-time"
},
"closedAt": {
"type": "string",
"format": "date-time",
"nullable": true
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"user": {
"$ref": "#/components/schemas/SupportTicketUserResponseDto"
},
"latestMessage": {
"nullable": true,
"description": "Latest message preview only; complete history is not included.",
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/SupportTicketMessageResponseDto"
}
]
}
},
"required": [
"id",
"userId",
"subject",
"category",
"priority",
"status",
"lastMessageAt",
"createdAt",
"user"
]
},
"ReplySupportTicketDto": {
"type": "object",
"properties": {