AiQlick Support/Help Desk System — Complete Documentation
Status: Frontend fully implemented. Backend implementation required. Date: March 2026 Backend specification: See
DOCS/SUPPORT_SYSTEM.mdfor detailed backend-only spec (database DDL, resolvers, etc.)
Table of Contents
- System Overview
- Architecture Diagram
- User Flows (Visual)
- Access Control & Roles
- Frontend Implementation — Complete File Map
- Frontend Components Detail
- GraphQL API Contract
- Ticket Lifecycle
- FAQ System Flow
- Backend Implementation Steps
- Modified Existing Files
- Checklist — What's Done vs What's Needed
GraphQL Type Convention: The backend uses Output-suffixed type names (e.g.,
SupportTicketOutput,TicketMessageOutput). Frontend fragments target these types. See Section 7 for details.
1. System Overview
The support system replaces the old "Need Help?" mailto link with a full ticket-based help desk. It has three tiers:
| Tier | Audience | Route | Purpose |
|---|---|---|---|
| User-Facing | All authenticated users | Sidebar drawer + /support | Submit tickets, view FAQs, track ticket status |
| Support Portal | Support agents + super admins | /support-portal | Manage ticket queue, reply, internal notes, FAQ management |
| Admin Panel | Super admins only | /admin/support | Overview dashboard, agent management, global audit trail |
Key Design Principles
- Agent identity masking — Users always see "AiQlick Support" as the replier, never individual agent names. The backend returns real sender data; the frontend masks it in user-facing views.
- Internal notes — Support agents can add internal notes visible only to the support team, never shown to users.
- Full audit trail — Every action (status change, assignment, message, note) is logged with actor identity and timestamp.
- FAQ from resolved tickets — When support resolves/closes a ticket, they're prompted to document it as a FAQ entry, optionally using AI to auto-generate the Q&A.
- Graceful error handling — All hooks handle GraphQL errors gracefully, showing empty states when the backend isn't ready.
2. Architecture Diagram
┌─────────────────────────────────────────────────────────────────────┐
│ FRONTEND (Next.js 15) │
│ │
│ ┌──────────────┐ ┌──────────────────┐ ┌───────────────────────┐ │
│ │ User-Facing │ │ Support Portal │ │ Admin Panel │ │
│ │ │ │ │ │ │ │
│ │ SupportDrawer│ │ /support-portal │ │ /admin/support │ │
│ │ /support │ │ ┌──────────────┐ │ │ ┌─────────────────┐ │ │
│ │ │ │ │TicketQueue │ │ │ │AdminSupOverview │ │ │
│ │ ┌──────────┐│ │ │TicketDetail │ │ │ │AdminSupAgents │ │ │
│ │ │FAQ Tab ││ │ │SupportDash │ │ │ │Global AuditLog │ │ │
│ │ │Ticket Tab││ │ │FAQ Manager │ │ │ └─────────────────┘ │ │
│ │ │New Ticket││ │ │AuditLogView │ │ │ │ │
│ │ └──────────┘│ │ │ResolveFaqMdl │ │ │ │ │
│ └──────────────┘ │ └──────────────┘ │ └───────────────────────┘ │
│ └──────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Shared Layer │ │
│ │ lib/hooks/useSupport.ts — All queries, mutations, hooks │ │
│ │ lib/types/support.ts — TypeScript interfaces & enums │ │
│ │ graphql/operations/support/ — Queries, mutations, subs │ │
│ │ graphql/operations/schema/support/ — Fragments │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ Apollo Client (GraphQL) │
└──────────────────────────────┼───────────────────────────────────────┘
│
┌──────────▼──────────┐
│ BACKEND (Node.js) │
│ │
│ GraphQL API Server │
│ ┌────────────────┐ │
│ │ SupportTicket │ │
│ │ TicketMessage │ │
│ │ TicketAuditLog │ │
│ │ FaqEntry │ │
│ │ UserRole │ │
│ └────────────────┘ │
│ │
│ PostgreSQL Database │
└──────────────────────┘
3. User Flows (Visual)
3a. User Creates a Support Ticket
┌──────────────┐ ┌───────────────────┐ ┌─────────────────────┐
│ User clicks │ │ SupportDrawer │ │ "New Ticket" tab │
│ "Need help?" │────▶│ opens (modal) │────▶│ Category, Priority │
│ in sidebar │ │ FAQs tab shown │ │ Subject, Desc │
└──────────────┘ │ first │ └─────────┬───────────┘
└───────────────────┘ │
▼
┌───────────────────┐ ┌─────────────────────┐
│ User can also │ │ createSupportTicket│
│ click "Open full │ │ mutation called │
│ Support Center" │ │ Status → OPEN │
│ → /support page │ │ Toast: "Created" │
└───────────────────┘ └─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Redirected to │
│ Conversation view │
│ (within drawer or │
│ /support page) │
└─────────────────────┘
3b. User Checks FAQs Before Creating Ticket
┌──────────────┐ ┌───────────────────┐ ┌─────────────────────┐
│ User clicks │ │ SupportDrawer │ │ FAQs tab │
│ "Need help?" │────▶│ opens │────▶│ Search bar │
└──────────────┘ │ FAQs tab first │ │ Category filters │
└───────────────────┘ │ Expandable cards │
└─────────┬───────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌──────────┐
│ FAQ answers │ │ "Was this │ │ "Still │
│ the question│ │ helpful?" │ │ need │
│ → User done │ │ thumbs up │ │ help?" │
└────────────┘ └────────────┘ └────┬─────┘
│
▼
┌────────────┐
│ Switches to│
│ "New Ticket"│
│ tab │
└────────────┘
3c. Support Agent Handles a Ticket
┌──────────────────┐ ┌────────────────────┐ ┌──────────────────────┐
│ Agent opens │ │ Ticket Queue │ │ Ticket Detail View │
│ /support-portal │────▶│ Filter by status, │────▶│ ┌────────────────┐ │
│ (profile menu) │ │ priority, search │ │ │ Conversation │ │
└──────────────────┘ └────────────────────┘ │ │ tab │ │
│ ├────────────────┤ │
│ │ Audit Log tab │ │
│ └────────────────┘ │
│ │
│ Right sidebar: │
│ • Status selector │
│ • Priority selector │
│ • Category selector │
│ • Assign to agent │
│ • "Assign to Me" │
│ • Resolve / Close │
└──────────┬───────────┘
│
┌─────────────────────┤
▼ ▼
┌────────────┐ ┌──────────────────┐
│ Reply to │ │ Add Internal │
│ user │ │ Note (checkbox) │
│ (visible) │ │ (hidden from │
└────────────┘ │ user, amber bg) │
└──────────────────┘
3d. Ticket Resolution → FAQ Creation Flow
┌──────────────────┐ ┌────────────────────────────────────────────────┐
│ Agent clicks │ │ TicketResolveFaqModal opens │
│ "Resolve Ticket" │────▶│ │
│ or "Close Ticket"│ │ ┌──────────────────────────────────────────┐ │
└──────────────────┘ │ │ "Use AI to Summarize" button │ │
│ │ (calls generateFaqSuggestion mutation) │ │
│ │ Pre-fills question, answer, tags │ │
│ └──────────────────────────────────────────┘ │
│ │
│ Fields: │
│ • Question (pre-filled from ticket subject) │
│ • Answer (editable textarea) │
│ • Category (auto-set from ticket category) │
│ • Tags (Enter to add, click to remove) │
│ • Status: "Save as Draft" or "Publish" │
│ │
│ Actions: │
│ ┌─────────────────────┐ ┌───────────────┐ │
│ │ "Skip — Not FAQ- │ │ "Save FAQ" │ │
│ │ worthy" (resolves │ │ (creates FAQ │ │
│ │ ticket without FAQ) │ │ then resolves│ │
│ └─────────────────────┘ │ ticket) │ │
│ └───────────────┘ │
└────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────┐
│ Ticket status updated (RESOLVED or CLOSED) │
│ FAQ entry created (if not skipped) │
│ Audit log entry added │
│ Toast notification shown │
└────────────────────────────────────────────────┘
3e. Admin Overview & Agent Management
┌──────────────────┐ ┌────────────────────────────────────────────────┐
│ Super Admin │ │ /admin/support — 3 Tabs │
│ navigates to │────▶│ │
│ Admin > Support │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
└──────────────────┘ │ │ Overview │ │ Tickets │ │ Agents │ │
│ │ │ │ │ │ │ │
│ │ Stat │ │ All │ │ List agents │ │
│ │ cards: │ │ tickets │ │ + "Add │ │
│ │ Open, │ │ with │ │ Agent" │ │
│ │ InProg, │ │ admin │ │ (user │ │
│ │ Waiting, │ │ actions │ │ search + │ │
│ │ Resolved │ │ │ │ assign │ │
│ │ │ │ │ │ role) │ │
│ │ Agent │ │ │ │ "Remove │ │
│ │ perf │ │ │ │ Agent" │ │
│ │ table │ │ │ │ (confirm) │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
└────────────────────────────────────────────────┘
3f. Navigation Flow — Who Goes Where
┌────────────────────────────────────────────────────────────────────────────┐
│ Profile Dropdown Menu │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Admin Panel (super admin only → /admin) │ │
│ │ Support Portal (support agent + super admin → /support-portal│ │
│ │ ──────────────────────────────────────────── │ │
│ │ Team → /company/team/members │ │
│ │ Profile → /userprofile │ │
│ │ Settings → /userprofile?tab=account │ │
│ │ Developers Tools (super admin only → /devtools) │ │
│ │ Sign out │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Sidebar Footer: "Need help?" button │ │
│ │ → Opens SupportDrawer (modal) with FAQs, My Tickets, New Ticket │ │
│ │ → Footer link: "Open full Support Center" → /support │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────┘
Access Matrix:
┌──────────────────────┬──────────────┬───────────────┬──────────────┐
│ Route │ Regular User │ Support Agent │ Super Admin │
├──────────────────────┼──────────────┼───────────────┼──────────────┤
│ SupportDrawer │ Yes │ Yes │ Yes │
│ /support │ Yes │ Yes │ Yes │
│ /support-portal │ No │ Yes │ Yes │
│ /admin/support │ No │ No │ Yes │
└──────────────────────┴──────────────┴───────────────┴──────────────┘
4. Access Control & Roles
New Role: isSupportAgent
A new boolean computed field on the User type. On the backend this should be determined by checking if the user has a "support" role in a global roles table (distinct from company-scoped roles).
Frontend changes made:
lib/types/user.ts— AddedisSupportAgent?: booleanto User typegraphql/operations/user/queries.ts— Field commented out in WHO_AM_I query (waiting for backend)contexts/UserAuthProvider.tsx—isSupportAgentexposed in auth context, defaults tofalseuntil backend returns it
Backend needs to:
- Create a
UserRoletable (global, not company-scoped) - Add a
supportrole entry - Add
isSupportAgentcomputed field to the User GraphQL type - Return
isSupportAgentin thewhoAmIquery response
Route Guards
| Guard | File | Logic |
|---|---|---|
SupportPortalGuard | components/support/layout/SupportPortalGuard.tsx | user.isSupportAgent || user.isSuperAdmin — redirects to / otherwise |
AdminGuard (updated) | components/admin/layout/AdminGuard.tsx | Added "support" to AdminSection type — super admin only |
5. Frontend Implementation — Complete File Map
New Files Created
# Types & Data
lib/types/support.ts — All TypeScript interfaces, enums, display helpers
lib/hooks/useSupport.ts — All React hooks for support system
# GraphQL Operations
graphql/operations/schema/support/fragments.ts — 6 fragments (ticketUser, ticket, attachment, message, audit, faq)
graphql/operations/support/queries.ts — 11 queries (user, support, admin, faq)
graphql/operations/support/mutations.ts — 16 mutations (user, support, admin, faq, AI)
graphql/operations/support/subscriptions.ts — 2 subscriptions (user ticket update, support event)
# User-Facing Components
components/support/SupportDrawer.tsx — Modal with 3 tabs: FAQs, My Tickets, New Ticket
components/support/NewTicketForm.tsx — Ticket creation form
components/support/TicketList.tsx — Ticket list with loading skeleton
components/support/TicketConversation.tsx — Message thread with agent masking, context-aware alignment, initial description display
components/support/TicketStatusChip.tsx — Status badge (TWChip)
components/support/TicketPriorityChip.tsx — Priority badge (TWChip)
components/support/FaqCard.tsx — Expandable FAQ card with tags display and helpful vote
components/support/FaqList.tsx — Searchable FAQ list with debounced search (300ms), category filters
# Support Portal Components
components/support/layout/SupportPortalGuard.tsx — Route guard (isSupportAgent || isSuperAdmin)
components/support/layout/SupportPortalLayout.tsx — Flex layout with sidebar
components/support/layout/SupportPortalSidebar.tsx — Navigation: Dashboard, Tickets, FAQs
components/support/portal/SupportDashboard.tsx — Stats cards and metrics
components/support/portal/TicketQueue.tsx — Filterable ticket table with "My Tickets" support (assignedToMe query param)
components/support/portal/TicketDetailView.tsx — Full ticket view with conversation + audit
components/support/portal/TicketAuditLogView.tsx — Timeline of audit entries
components/support/portal/TicketResolveFaqModal.tsx — FAQ creation modal on resolve/close with AI
# Admin Components
components/admin/support/AdminSupportOverview.tsx — Overview stats + agent performance table
components/admin/support/AdminSupportAgents.tsx — Agent list with add/remove functionality
# Route Pages
app/(shared)/support/page.tsx — Full support page (user-facing)
app/(shared)/support/[ticketId]/page.tsx — Individual ticket page
app/support-portal/layout.tsx — Portal layout wrapper
app/support-portal/page.tsx — Portal dashboard
app/support-portal/tickets/page.tsx — Ticket queue page
app/support-portal/tickets/[ticketId]/page.tsx — Ticket detail page
app/support-portal/faqs/page.tsx — FAQ management page with create/edit/delete, debounced search
app/admin/support/page.tsx — Admin support page (3 tabs)
# Documentation
DOCS/SUPPORT_SYSTEM.md — Backend specification (1636 lines)
DOCS/SUPPORT_SYSTEM_COMPLETE.md — This document
Modified Existing Files
| File | Change |
|---|---|
lib/types/user.ts | Added isSupportAgent?: boolean |
graphql/operations/user/queries.ts | Added commented isSupportAgent field in WHO_AM_I |
contexts/UserAuthProvider.tsx | Exposed isSupportAgent in auth context |
graphql/operations/schema/index.ts | Added export * from './support/fragments' |
components/navigation/Sidebar.tsx | Replaced mailto with SupportDrawer trigger + unread badge |
components/navigation/UserProfile.tsx | Added "Support Portal" link in profile dropdown (under Admin Panel) |
components/profile/Sidebar.tsx | Replaced mailto with SupportDrawer trigger |
components/admin/layout/AdminGuard.tsx | Added "support" to AdminSection type + permissions |
components/admin/layout/AdminSidebar.tsx | Added Support nav item under "Engagement" group |
components/admin/layout/AdminLayout.tsx | Added page title mapping for /admin/support |
6. Frontend Components Detail
SupportDrawer (User-Facing Modal)
Trigger: "Need help?" button in sidebar footer Tabs:
- FAQs (shown first) — Searchable FAQ list (debounced 300ms) with category filters, expandable cards showing tags, helpful voting, "Still need help?" button switches to New Ticket tab
- My Tickets — List of user's tickets with status chips, click to open conversation
- New Ticket — Form: category (select), priority (select), subject (input), description (textarea)
Footer: "Open full Support Center" link → navigates to /support
Agent Identity Masking: In conversation view, all support replies show "AiQlick Support" with a generic avatar — individual agent names are never visible to users.
Message Alignment: Message alignment is context-aware. In user views, user messages are right-aligned (own messages) and support messages are left-aligned. In the support portal, this is reversed — support agent messages are right-aligned and user messages are left-aligned.
Ticket Description: The initial ticket description is rendered as the first message in the conversation thread (before any messages), showing the ticket creator's avatar and a "Description" chip.
TicketDetailView (Support Portal)
Layout: Two-panel — main content (left) + metadata sidebar (right)
Left panel tabs:
- Conversation — Full message thread including initial ticket description, internal notes (amber background), and context-aware message alignment (support messages right-aligned in portal view)
- Audit Log — Timeline of all actions with actor identity
Internal Note Toggle: Checkbox below conversation — "Send as internal note (not visible to user)"
Right sidebar:
- Status, Priority, Category selectors (TWSelect)
- Assigned To selector + "Assign to Me" button (hidden when ticket is already assigned to the current user)
- Ticket metadata (created by, dates)
- "Resolve Ticket" → opens FAQ creation modal (hidden when status is RESOLVED or CLOSED)
- "Close Ticket" → opens FAQ creation modal (hidden when status is CLOSED)
TicketResolveFaqModal (FAQ from Resolved Ticket)
Triggered by: "Resolve Ticket" or "Close Ticket" in TicketDetailView
Features:
- "Use AI to Summarize" button — Calls
generateFaqSuggestionmutation, pre-fills question/answer/tags - Question input (pre-filled from ticket subject)
- Answer textarea (editable)
- Category selector (auto-set from ticket category)
- Tags input (Enter to add, click to remove chips)
- FAQ Status: "Save as Draft" or "Publish Immediately"
- "Skip — Not FAQ-worthy" button (resolves/closes without creating FAQ)
- "Save FAQ" button (creates FAQ entry, then updates ticket status)
SupportPortalSidebar
Navigation items: Dashboard, All Tickets, My Tickets, FAQs
- "My Tickets" navigates to
/support-portal/tickets?assignedToMe=true— the TicketQueue reads this search param and passesassignedToId: user.idto the backend query, filtering to only tickets assigned to the current agent. The page title dynamically shows "My Tickets" vs "All Tickets".
Footer links:
- "Admin Panel" (super admin only)
- "Back to App" →
/company/dashboardor/jobseeker
7. GraphQL API Contract
Fragments
| Fragment | Type | Used In |
|---|---|---|
TicketUserFields | TicketUser | Embedded user data (createdBy, assignedTo, sender, actor) |
SupportTicketFields | SupportTicketOutput | All ticket views (list + detail, includes createdBy/assignedTo) |
TicketAttachmentFields | TicketAttachmentOutput | Message attachments |
TicketMessageFields | TicketMessageOutput | Conversations (includes sender, attachments) |
TicketAuditLogFields | TicketAuditLogOutput | Audit views (includes actor) |
FaqEntryFields | FaqEntryOutput | FAQ lists and detail |
Queries (11 total)
User-Facing:
| Query | Variables | Returns | Auth |
|---|---|---|---|
myTickets | MyTicketsInput? | PaginatedTickets | Authenticated user |
myTicketDetail | ticketId: ID! | { ticket, messages, auditLog } | Ticket owner only |
myUnreadTicketCount | — | { count: Int } | Authenticated user |
Support Portal:
| Query | Variables | Returns | Auth |
|---|---|---|---|
supportTickets | SupportTicketsInput! | PaginatedTickets | Support agent or super admin |
supportTicketDetail | ticketId: ID! | { ticket, messages, auditLog } | Support agent or super admin |
supportDashboardStats | — | SupportDashboardStats | Support agent or super admin |
Admin:
| Query | Variables | Returns | Auth |
|---|---|---|---|
adminSupportOverview | — | AdminSupportOverview | Super admin |
adminListSupportAgents | — | [SupportAgent] | Super admin |
adminSupportAuditLog | AdminSupportAuditLogInput? | PaginatedAuditLog | Super admin |
FAQ:
| Query | Variables | Returns | Auth |
|---|---|---|---|
publishedFaqs | PublishedFaqsInput? | PaginatedFaqs | Any authenticated user |
supportFaqs | SupportFaqsInput? | PaginatedFaqs | Support agent or super admin |
Mutations (16 total)
User-Facing (4):
| Mutation | Variables | Returns | Side Effects |
|---|---|---|---|
createSupportTicket | CreateSupportTicketInput! | SupportTicket | Creates audit log (CREATED), sets status OPEN |
sendTicketMessage | SendTicketMessageInput! | TicketMessage | Creates audit log (MESSAGE_SENT), updates lastActivityAt |
closeMyTicket | ticketId: ID! | SupportTicket | Creates audit log (CLOSED), sets closedAt |
reopenMyTicket | ticketId: ID! | SupportTicket | Creates audit log (REOPENED), sets status OPEN |
Support Portal (6):
| Mutation | Variables | Returns | Side Effects |
|---|---|---|---|
assignTicket | ticketId: ID!, agentId: ID! | SupportTicket | Audit log (ASSIGNED), status → IN_PROGRESS |
updateTicketStatus | ticketId: ID!, status: TicketStatus! | SupportTicket | Audit log (STATUS_CHANGED), set resolvedAt/closedAt |
updateTicketPriority | ticketId: ID!, priority: TicketPriority! | SupportTicket | Audit log (PRIORITY_CHANGED) |
updateTicketCategory | ticketId: ID!, category: TicketCategory! | SupportTicket | Audit log (CATEGORY_CHANGED) |
sendSupportReply | SendSupportReplyInput! | TicketMessage | Audit log (MESSAGE_SENT), senderRole=SUPPORT, set firstResponseAt if first |
addInternalNote | AddInternalNoteInput! | TicketMessage | Audit log (INTERNAL_NOTE_ADDED), isInternalNote=true |
Admin (2):
| Mutation | Variables | Returns | Side Effects |
|---|---|---|---|
adminAssignSupportRole | userId: ID! | { success, message } | Adds "support" role to user |
adminRevokeSupportRole | userId: ID! | { success, message } | Removes "support" role from user |
FAQ (4):
| Mutation | Variables | Returns | Side Effects |
|---|---|---|---|
createFaqEntry | CreateFaqInput! | FaqEntry | Links to sourceTicketId if provided |
updateFaqEntry | id: ID!, UpdateFaqInput! | FaqEntry | |
deleteFaqEntry | id: ID! | { success, message } | Admin only |
markFaqHelpful | id: ID! | { id, helpfulCount } | Increments helpfulCount |
AI (1):
| Mutation | Variables | Returns | Notes |
|---|---|---|---|
generateFaqSuggestion | ticketId: ID! | { question, answer, tags } | AI-powered; reads ticket conversation, generates Q&A summary |
Subscriptions (2)
| Subscription | Variables | Payload | Auth |
|---|---|---|---|
myTicketUpdated | ticketId: ID? | { ticket, newMessage?, type } | Authenticated user (own tickets only) |
supportTicketEvent | — | { ticket, type } | Support agent or super admin |
Input Types
input CreateSupportTicketInput {
subject: String!
description: String!
category: TicketCategory!
priority: TicketPriority # defaults to MEDIUM
}
input SendTicketMessageInput {
ticketId: ID!
content: String!
}
input SendSupportReplyInput {
ticketId: ID!
content: String!
}
input AddInternalNoteInput {
ticketId: ID!
content: String!
}
input SupportTicketsInput {
status: TicketStatus
priority: TicketPriority
category: TicketCategory
assignedToId: ID
search: String
sortBy: TicketSortBy # CREATED_AT | LAST_ACTIVITY | PRIORITY
sortOrder: SortOrder # ASC | DESC
page: Int
limit: Int
}
input MyTicketsInput {
status: TicketStatus
page: Int
limit: Int
}
input CreateFaqInput {
question: String!
answer: String!
category: TicketCategory!
tags: [String!]
sourceTicketId: ID
status: FaqStatus # DRAFT | PUBLISHED
}
input UpdateFaqInput {
question: String
answer: String
category: TicketCategory
tags: [String!]
status: FaqStatus
}
input PublishedFaqsInput {
category: TicketCategory
search: String
page: Int
limit: Int
}
input SupportFaqsInput {
status: FaqStatus
category: TicketCategory
search: String
page: Int
limit: Int
}
8. Ticket Lifecycle
Status Flow Diagram
┌──────────────────┐
│ User creates │
│ ticket │
└────────┬─────────┘
│
▼
┌──────────────────┐
┌─────────│ OPEN │◄────────────────────────┐
│ └────────┬─────────┘ │
│ │ │
│ Agent assigns Customer replies
│ │ to RESOLVED ticket
│ ▼ │
│ ┌──────────────────┐ │
│ │ IN_PROGRESS │◄──────────────┐ │
│ └────────┬─────────┘ │ │
│ │ │ │
│ Agent replies Customer replies │
│ │ │ │
│ ▼ │ │
│ ┌──────────────────┐ │ │
│ │ WAITING_ON_ │───────────────┘ │
│ │ CUSTOMER │ │
│ └────────┬─────────┘ │
│ │ │
│ Agent resolves │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ RESOLVED │──────────────────────────┘
│ └────────┬─────────┘
│ │
│ Agent/User closes
│ or auto-close (7 days)
│ │
│ ▼
│ ┌──────────────────┐
└─────────▶│ CLOSED │
└──────────────────┘
Status Transition Table
| From | To | Triggered By | Audit Action |
|---|---|---|---|
| — | OPEN | User creates ticket | CREATED |
| OPEN | IN_PROGRESS | Agent assigns ticket | ASSIGNED |
| IN_PROGRESS | WAITING_ON_CUSTOMER | Agent replies | STATUS_CHANGED |
| WAITING_ON_CUSTOMER | IN_PROGRESS | Customer replies | STATUS_CHANGED |
| IN_PROGRESS | RESOLVED | Agent resolves | RESOLVED |
| WAITING_ON_CUSTOMER | RESOLVED | Agent resolves | RESOLVED |
| OPEN | RESOLVED | Agent resolves | RESOLVED |
| RESOLVED | OPEN | Customer replies to resolved | REOPENED |
| RESOLVED | CLOSED | Agent/User closes | CLOSED |
| OPEN | CLOSED | Agent/User closes | CLOSED |
| IN_PROGRESS | CLOSED | Agent closes | CLOSED |
| RESOLVED | CLOSED | Auto-close after 7 days | CLOSED |
| CLOSED | OPEN | User reopens | REOPENED |
Automatic Status Changes (Backend Logic)
| Trigger | Action |
|---|---|
| Agent sends first reply | Set firstResponseAt if null |
| Agent sends reply | Status → WAITING_ON_CUSTOMER |
User replies to WAITING_ON_CUSTOMER | Status → IN_PROGRESS |
User replies to RESOLVED | Status → OPEN (reopened) |
Status changes to RESOLVED | Set resolvedAt |
Status changes to CLOSED | Set closedAt |
7 days after RESOLVED with no activity | Auto-close → CLOSED |
9. FAQ System Flow
Overview
┌──────────────────────────────────────────────────────────────────────────┐
│ FAQ Lifecycle │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ ┌───────────┐ │
│ │ Ticket │ │ Agent clicks │ │ FAQ Modal │ │ FAQ Entry │ │
│ │ Resolved/ │───▶│ Resolve or │───▶│ opens with │───▶│ created │ │
│ │ Closed │ │ Close button │ │ AI option │ │ as DRAFT │ │
│ └─────────────┘ └──────────────┘ └────────────┘ │ or PUB │ │
│ └─────┬─────┘ │
│ │ │
│ ┌───────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ FAQ appears in: │ │
│ │ • SupportDrawer FAQs tab (PUBLISHED only) │ │
│ │ • /support page FAQs tab (PUBLISHED only) │ │
│ │ • /support-portal/faqs (ALL statuses — support team) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ User Interaction: │
│ • Search FAQs by keyword (debounced 300ms) │
│ • Filter by category │
│ • Expand to read answer │
│ • View tags on each FAQ card │
│ • "Was this helpful?" thumbs up → markFaqHelpful mutation │
│ • "Still need help?" → switches to New Ticket tab │
└──────────────────────────────────────────────────────────────────────────┘
AI FAQ Suggestion Flow
Agent clicks "Use AI to Summarize"
│
▼
generateFaqSuggestion(ticketId) mutation
│
▼
Backend reads all ticket messages
│
▼
AI service summarizes conversation into:
• question: distilled user question
• answer: clear resolution summary
• tags: relevant keywords
│
▼
Frontend pre-fills the FAQ form fields
Agent reviews, edits, and saves
The frontend calls the generateFaqSuggestion mutation. If the backend AI service is unavailable, the hook shows a warning toast and the agent can write the FAQ manually.
10. Backend Implementation Steps
Step 1: Database Schema
Create these tables in PostgreSQL (full DDL in DOCS/SUPPORT_SYSTEM.md):
SupportTicket— Main ticket table with status, priority, category, assignmentTicketMessage— Messages within tickets (user, support, system roles)TicketAttachment— File attachments on messages (S3 integration)TicketAuditLog— Audit trail of all actionsFaqEntry— FAQ knowledge base entriesUserRole(if not exists) — Global roles table for "support" role
Also:
- Ticket number sequence:
CREATE SEQUENCE support_ticket_seq START 1; - Full-text search index on ticket subject + FAQ question/answer
- Indexes for common query patterns (status, assignedTo, createdBy, category)
Step 2: GraphQL Type Definitions
Add these to the GraphQL schema SDL:
Enums: TicketCategory, TicketPriority, TicketStatus, TicketMessageSenderRole, TicketAuditAction, FaqStatus, TicketSortBy, SortOrder
Types: SupportTicket, TicketMessage, TicketAttachment, TicketAuditLog, FaqEntry, SupportAgent, SupportDashboardStats, AdminSupportOverview, AgentPerformance, PaginatedTickets, PaginatedFaqs, TicketDetail
Inputs: CreateSupportTicketInput, SendTicketMessageInput, SendSupportReplyInput, AddInternalNoteInput, SupportTicketsInput, MyTicketsInput, CreateFaqInput, UpdateFaqInput, AdminSupportAuditLogInput
See Section 7 of this document for the complete list of queries, mutations, and subscriptions.
Step 3: Resolvers — User-Facing
| Resolver | Key Logic |
|---|---|
myTickets | Filter by createdById = currentUser.id, paginate |
myTicketDetail | Verify createdById = currentUser.id, return ticket + messages (exclude isInternalNote=true) |
myUnreadTicketCount | Count tickets with messages newer than user's last read timestamp |
createSupportTicket | Generate ticket number (SUP-XXXXX), set status OPEN, create CREATED audit log |
sendTicketMessage | Verify user owns ticket, create message with senderRole=USER, update lastActivityAt |
closeMyTicket | Verify user owns ticket, set status CLOSED, closedAt, create CLOSED audit log |
reopenMyTicket | Verify user owns ticket, set status OPEN, create REOPENED audit log |
Step 4: Resolvers — Support Portal
| Resolver | Key Logic |
|---|---|
supportTickets | Auth: isSupportAgent || isSuperAdmin. Full filter/sort/search/paginate |
supportTicketDetail | Return ticket + ALL messages (including internal notes) + audit log |
supportDashboardStats | Aggregate counts by status, averages for response/resolution time |
assignTicket | Set assignedToId, status → IN_PROGRESS, create ASSIGNED audit log |
updateTicketStatus | Update status, set resolvedAt/closedAt as appropriate, create STATUS_CHANGED audit log |
sendSupportReply | Create message with senderRole=SUPPORT, set firstResponseAt if first, status → WAITING_ON_CUSTOMER |
addInternalNote | Create message with isInternalNote=true, senderRole=SUPPORT, create INTERNAL_NOTE_ADDED audit log |
Step 5: Resolvers — Admin
| Resolver | Key Logic |
|---|---|
adminSupportOverview | Dashboard stats + agent performance metrics (assigned, resolved, avg times) |
adminListSupportAgents | List all users with "support" role, include ticket counts |
adminSupportAuditLog | Global audit log, filterable by ticket/agent/action/date |
adminAssignSupportRole | Add "support" role to user in UserRole table |
adminRevokeSupportRole | Remove "support" role from user |
Step 6: Resolvers — FAQ
| Resolver | Key Logic |
|---|---|
publishedFaqs | Filter by status=PUBLISHED, support search on question/answer/tags |
supportFaqs | All statuses, auth: support agent or super admin |
createFaqEntry | Create entry, link sourceTicketId if provided |
updateFaqEntry | Auth: creator or super admin |
deleteFaqEntry | Auth: super admin only |
markFaqHelpful | Increment helpfulCount (consider de-duplication via FaqVote table) |
generateFaqSuggestion | Call AI service to summarize ticket conversation into Q&A format |
Step 7: Subscriptions
| Subscription | Implementation |
|---|---|
myTicketUpdated | PubSub: publish on any ticket update where user is ticket creator. Filter by ticketId if provided |
supportTicketEvent | PubSub: publish on new ticket created, new message, status change. Auth: support agent or super admin |
Step 8: Audit Log Integration
Every mutation that modifies a ticket should create an audit log entry:
await prisma.ticketAuditLog.create({
data: {
ticketId,
actorId: currentUser.id,
action: 'STATUS_CHANGED',
previousValue: oldStatus,
newValue: newStatus,
metadata: { reason: '...' }
}
})
Step 9: Auto-Close Cron Job
Set up a scheduled job (cron or similar) to auto-close RESOLVED tickets after 7 days:
UPDATE "SupportTicket"
SET "status" = 'CLOSED',
"closedAt" = NOW(),
"updatedAt" = NOW()
WHERE "status" = 'RESOLVED'
AND "lastActivityAt" < NOW() - INTERVAL '7 days';
Step 10: WHO_AM_I Integration
Add isSupportAgent to the User type resolver:
// In User type resolvers
isSupportAgent: async (user, _, context) => {
const role = await prisma.userRole.findFirst({
where: { userId: user.id, role: 'support' }
})
return !!role
}
Then uncomment the field in the frontend's WHO_AM_I query (graphql/operations/user/queries.ts).
11. Modified Existing Files
Summary of All Frontend Changes to Existing Files
| File | What Changed | Why |
|---|---|---|
| lib/types/user.ts | Added isSupportAgent?: boolean | New role for support agents |
| graphql/operations/user/queries.ts | Added commented isSupportAgent field | Waiting for backend to add field |
| contexts/UserAuthProvider.tsx | Added isSupportAgent to context type and value | Expose new role to all components |
| graphql/operations/schema/index.ts | Added export * from './support/fragments' | Register support fragments |
| components/navigation/Sidebar.tsx | Replaced mailto link with SupportDrawer + unread badge | New "Need help?" interaction |
| components/profile/Sidebar.tsx | Replaced mailto link with SupportDrawer + unread badge | Same for mobile sidebar |
| components/navigation/UserProfile.tsx | Added "Support Portal" link in profile dropdown | Navigation for support agents |
| components/admin/layout/AdminGuard.tsx | Added "support" to AdminSection type + permissions | Admin panel integration |
| components/admin/layout/AdminSidebar.tsx | Added Support nav item + SupportIcon SVG | Admin panel navigation |
| components/admin/layout/AdminLayout.tsx | Added page title for /admin/support | Page header mapping |
12. Checklist — What's Done vs What's Needed
Frontend (DONE)
- Types, interfaces, enums for entire support system
- GraphQL fragments, queries, mutations, subscriptions defined
- React hooks for all operations (queries, mutations, admin, FAQ)
- SupportDrawer modal (FAQs, My Tickets, New Ticket tabs)
- "Open full Support Center" link in drawer
-
/supportfull page (user-facing) -
/support/[ticketId]individual ticket page - Ticket conversation with agent identity masking
- Internal notes (visible only to support team, amber background)
- FAQ cards with search, category filters, helpful voting
- Support Portal guard (
isSupportAgent || isSuperAdmin) - Support Portal layout with sidebar navigation
- Support Portal dashboard with stats
- Ticket queue with filters (status, priority, category, search)
- Ticket detail view with conversation + audit log tabs
- Ticket resolve/close → FAQ creation modal with AI option
- FAQ management page in support portal with create/edit/delete modals
- FAQ tags displayed in published FAQ cards and admin table
- FAQ search debounced (300ms) in both user and admin views
- Ticket description displayed as initial message in conversation thread
- Message alignment context-aware (user vs support portal views)
- "My Tickets" filters by
assignedToIdusing URL search params - "Assign to Me" button hidden when already assigned to current user
- "Resolve/Close" buttons conditionally rendered based on ticket status
- TWSelect dropdown toggle fix (close on re-click)
- Admin support overview with stats + agent performance
- Admin agent management (add/remove support role)
- "Support Portal" link in profile dropdown (under Admin Panel)
- "Need help?" button with unread badge in sidebar
- Back to App →
/company/dashboard - Admin section registration (guard, sidebar, layout)
- Backend design document (1636 lines)
- Build passes with
yarn run build
Backend (TODO)
- Database: Create SupportTicket, TicketMessage, TicketAttachment, TicketAuditLog, FaqEntry tables
- Database: Create UserRole table (if not exists) for global "support" role
- Database: Ticket number sequence and indexes
- GraphQL SDL: Add all enums, types, inputs, queries, mutations, subscriptions
- Resolvers: User-facing (myTickets, createSupportTicket, sendTicketMessage, closeMyTicket, reopenMyTicket)
- Resolvers: Support portal (supportTickets, assignTicket, updateStatus, sendSupportReply, addInternalNote)
- Resolvers: Admin (adminSupportOverview, adminListSupportAgents, adminAssignSupportRole)
- Resolvers: FAQ (publishedFaqs, createFaqEntry, updateFaqEntry, markFaqHelpful)
- Resolvers: AI (generateFaqSuggestion — integrate with AI service)
- WHO_AM_I: Add
isSupportAgentcomputed field to User type - Audit Log: Implement audit log creation for every ticket mutation
- Subscriptions: myTicketUpdated, supportTicketEvent via PubSub
- Auto-close: Cron job to close RESOLVED tickets after 7 days
- Notifications: In-app notifications for new tickets, replies, assignments
- File Attachments: S3 integration for ticket message attachments
Frontend — Post-Backend (TODO)
- Uncomment
isSupportAgentin WHO_AM_I query - Test all flows end-to-end with real backend
- Add file attachment upload support (Dropzone in NewTicketForm)
- Wire up real-time subscriptions (WebSocket)
- Add notification badge for support agents (new tickets)
- Add email notifications (optional)
Appendix: Enum Values Reference
TicketCategory
GENERAL | BILLING | TECHNICAL | ACCOUNT | FEATURE_REQUEST | BUG_REPORT
TicketPriority
LOW | MEDIUM | HIGH | URGENT
TicketStatus
OPEN | IN_PROGRESS | WAITING_ON_CUSTOMER | RESOLVED | CLOSED
TicketMessageSenderRole
USER | SUPPORT | SYSTEM
TicketAuditAction
CREATED | STATUS_CHANGED | PRIORITY_CHANGED | ASSIGNED | UNASSIGNED | REASSIGNED | MESSAGE_SENT | INTERNAL_NOTE_ADDED | ATTACHMENT_ADDED | CATEGORY_CHANGED | RESOLVED | CLOSED | REOPENED
FaqStatus
DRAFT | PUBLISHED | ARCHIVED