From 8c89109213d39be8bba39e315fa3d4a3a4904336 Mon Sep 17 00:00:00 2001 From: barsa Date: Tue, 18 Nov 2025 14:06:27 +0900 Subject: [PATCH] Update worktree setup and enhance BFF with SupportModule integration - Changed worktree setup command from npm to pnpm for improved package management. - Added SupportModule to app.module.ts and router.config.ts for better support case handling. - Refactored OrderEventsService to utilize OrderUpdateEventPayload for improved type safety. - Updated InvoicesList component to use INVOICE_STATUS for status filtering and improved type definitions. - Enhanced SimActions and SimDetailsCard components to utilize SimStatus for better state management. - Refactored Subscription components to leverage new utility functions for status handling and billing cycle labels. - Improved SupportCasesView with better state management and error handling. - Updated API query keys to include support cases for better data retrieval. --- .cursor/worktrees.json | 2 +- apps/bff/src/app.module.ts | 2 + apps/bff/src/core/config/router.config.ts | 2 + .../orders/services/order-events.service.ts | 15 +- apps/bff/src/modules/support/index.ts | 3 + .../src/modules/support/support.controller.ts | 24 ++ .../bff/src/modules/support/support.module.ts | 10 + .../src/modules/support/support.service.ts | 140 ++++++++++ .../components/InvoiceList/InvoiceList.tsx | 26 +- .../features/orders/hooks/useOrderUpdates.ts | 21 +- .../src/features/orders/views/OrderDetail.tsx | 7 +- .../sim-management/components/SimActions.tsx | 18 +- .../components/SimDetailsCard.tsx | 11 +- .../components/SubscriptionCard.tsx | 60 +---- .../components/SubscriptionDetails.tsx | 68 +---- .../components/SubscriptionStatusBadge.tsx | 33 +-- .../subscriptions/utils/status-presenters.tsx | 59 +++++ .../views/SubscriptionDetail.tsx | 40 +-- .../subscriptions/views/SubscriptionsList.tsx | 28 +- .../features/support/hooks/useSupportCases.ts | 24 ++ .../support/views/SupportCasesView.tsx | 245 +++++++----------- apps/portal/src/lib/api/index.ts | 3 + docs/orders/ORDER-STATUS-UPDATES-STRATEGY.md | 3 +- packages/domain/index.ts | 1 + packages/domain/orders/events.ts | 26 ++ packages/domain/orders/index.ts | 1 + packages/domain/sim/helpers.ts | 18 ++ packages/domain/sim/index.ts | 1 + packages/domain/support/contract.ts | 27 ++ packages/domain/support/index.ts | 7 + packages/domain/support/schema.ts | 74 ++++++ 31 files changed, 620 insertions(+), 379 deletions(-) create mode 100644 apps/bff/src/modules/support/index.ts create mode 100644 apps/bff/src/modules/support/support.controller.ts create mode 100644 apps/bff/src/modules/support/support.module.ts create mode 100644 apps/bff/src/modules/support/support.service.ts create mode 100644 apps/portal/src/features/subscriptions/utils/status-presenters.tsx create mode 100644 apps/portal/src/features/support/hooks/useSupportCases.ts create mode 100644 packages/domain/orders/events.ts create mode 100644 packages/domain/sim/helpers.ts create mode 100644 packages/domain/support/contract.ts create mode 100644 packages/domain/support/index.ts create mode 100644 packages/domain/support/schema.ts diff --git a/.cursor/worktrees.json b/.cursor/worktrees.json index 77e9744d..e57556bc 100644 --- a/.cursor/worktrees.json +++ b/.cursor/worktrees.json @@ -1,5 +1,5 @@ { "setup-worktree": [ - "npm install" + "pnpm install" ] } diff --git a/apps/bff/src/app.module.ts b/apps/bff/src/app.module.ts index 87759385..76d4afbc 100644 --- a/apps/bff/src/app.module.ts +++ b/apps/bff/src/app.module.ts @@ -34,6 +34,7 @@ import { OrdersModule } from "@bff/modules/orders/orders.module"; import { InvoicesModule } from "@bff/modules/invoices/invoices.module"; import { SubscriptionsModule } from "@bff/modules/subscriptions/subscriptions.module"; import { CurrencyModule } from "@bff/modules/currency/currency.module"; +import { SupportModule } from "@bff/modules/support/support.module"; // System Modules import { HealthModule } from "@bff/modules/health/health.module"; @@ -85,6 +86,7 @@ import { HealthModule } from "@bff/modules/health/health.module"; InvoicesModule, SubscriptionsModule, CurrencyModule, + SupportModule, // === SYSTEM MODULES === HealthModule, diff --git a/apps/bff/src/core/config/router.config.ts b/apps/bff/src/core/config/router.config.ts index 71837cca..9af40a48 100644 --- a/apps/bff/src/core/config/router.config.ts +++ b/apps/bff/src/core/config/router.config.ts @@ -8,6 +8,7 @@ import { InvoicesModule } from "@bff/modules/invoices/invoices.module"; import { SubscriptionsModule } from "@bff/modules/subscriptions/subscriptions.module"; import { CurrencyModule } from "@bff/modules/currency/currency.module"; import { SecurityModule } from "@bff/core/security/security.module"; +import { SupportModule } from "@bff/modules/support/support.module"; export const apiRoutes: Routes = [ { @@ -21,6 +22,7 @@ export const apiRoutes: Routes = [ { path: "", module: InvoicesModule }, { path: "", module: SubscriptionsModule }, { path: "", module: CurrencyModule }, + { path: "", module: SupportModule }, { path: "", module: SecurityModule }, ], }, diff --git a/apps/bff/src/modules/orders/services/order-events.service.ts b/apps/bff/src/modules/orders/services/order-events.service.ts index fb7f57eb..122aac73 100644 --- a/apps/bff/src/modules/orders/services/order-events.service.ts +++ b/apps/bff/src/modules/orders/services/order-events.service.ts @@ -1,18 +1,7 @@ import { Injectable, Logger } from "@nestjs/common"; import type { MessageEvent } from "@nestjs/common"; import { Observable } from "rxjs"; - -export interface OrderUpdateEvent { - orderId: string; - status?: string; - activationStatus?: string | null; - message?: string; - reason?: string; - stage?: "started" | "in_progress" | "completed" | "failed"; - source?: string; - timestamp: string; - payload?: Record; -} +import type { OrderUpdateEventPayload } from "@customer-portal/domain/orders"; interface InternalObserver { next: (event: MessageEvent) => void; @@ -74,7 +63,7 @@ export class OrderEventsService { }); } - publish(orderId: string, update: OrderUpdateEvent): void { + publish(orderId: string, update: OrderUpdateEventPayload): void { const currentObservers = this.observers.get(orderId); if (!currentObservers || currentObservers.size === 0) { this.logger.debug("No active listeners for order update", { orderId }); diff --git a/apps/bff/src/modules/support/index.ts b/apps/bff/src/modules/support/index.ts new file mode 100644 index 00000000..72d6b55f --- /dev/null +++ b/apps/bff/src/modules/support/index.ts @@ -0,0 +1,3 @@ +export * from "./support.module"; +export * from "./support.controller"; +export * from "./support.service"; diff --git a/apps/bff/src/modules/support/support.controller.ts b/apps/bff/src/modules/support/support.controller.ts new file mode 100644 index 00000000..bdd07cd3 --- /dev/null +++ b/apps/bff/src/modules/support/support.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Get, Query, Request } from "@nestjs/common"; +import { SupportService } from "./support.service"; +import { ZodValidationPipe } from "@customer-portal/validation/nestjs"; +import { + supportCaseFilterSchema, + type SupportCaseFilter, + type SupportCaseList, +} from "@customer-portal/domain/support"; +import type { RequestWithUser } from "@bff/modules/auth/auth.types"; + +@Controller("support") +export class SupportController { + constructor(private readonly supportService: SupportService) {} + + @Get("cases") + async listCases( + @Request() _req: RequestWithUser, + @Query(new ZodValidationPipe(supportCaseFilterSchema)) + filters: SupportCaseFilter + ): Promise { + void _req; + return this.supportService.listCases(filters); + } +} diff --git a/apps/bff/src/modules/support/support.module.ts b/apps/bff/src/modules/support/support.module.ts new file mode 100644 index 00000000..f5e78d4a --- /dev/null +++ b/apps/bff/src/modules/support/support.module.ts @@ -0,0 +1,10 @@ +import { Module } from "@nestjs/common"; +import { SupportController } from "./support.controller"; +import { SupportService } from "./support.service"; + +@Module({ + controllers: [SupportController], + providers: [SupportService], + exports: [SupportService], +}) +export class SupportModule {} diff --git a/apps/bff/src/modules/support/support.service.ts b/apps/bff/src/modules/support/support.service.ts new file mode 100644 index 00000000..efb44918 --- /dev/null +++ b/apps/bff/src/modules/support/support.service.ts @@ -0,0 +1,140 @@ +import { Injectable } from "@nestjs/common"; +import { + SUPPORT_CASE_CATEGORY, + SUPPORT_CASE_PRIORITY, + SUPPORT_CASE_STATUS, + supportCaseFilterSchema, + supportCaseListSchema, + type SupportCase, + type SupportCaseFilter, + type SupportCaseList, + type SupportCasePriority, + type SupportCaseStatus, +} from "@customer-portal/domain/support"; + +const OPEN_STATUSES: SupportCaseStatus[] = [ + SUPPORT_CASE_STATUS.OPEN, + SUPPORT_CASE_STATUS.IN_PROGRESS, + SUPPORT_CASE_STATUS.WAITING_ON_CUSTOMER, +]; + +const RESOLVED_STATUSES: SupportCaseStatus[] = [ + SUPPORT_CASE_STATUS.RESOLVED, + SUPPORT_CASE_STATUS.CLOSED, +]; + +const HIGH_PRIORITIES: SupportCasePriority[] = [ + SUPPORT_CASE_PRIORITY.HIGH, + SUPPORT_CASE_PRIORITY.CRITICAL, +]; + +@Injectable() +export class SupportService { + // Placeholder dataset until Salesforce integration is ready + private readonly cases: SupportCase[] = [ + { + id: 12001, + subject: "VPS Performance Issues", + status: SUPPORT_CASE_STATUS.IN_PROGRESS, + priority: SUPPORT_CASE_PRIORITY.HIGH, + category: SUPPORT_CASE_CATEGORY.TECHNICAL, + createdAt: "2025-08-14T10:30:00Z", + updatedAt: "2025-08-15T14:20:00Z", + lastReply: "2025-08-15T14:20:00Z", + description: "Experiencing slow response times on VPS server, CPU usage appears high.", + assignedTo: "Technical Support Team", + }, + { + id: 12002, + subject: "Billing Question - Invoice #12345", + status: SUPPORT_CASE_STATUS.WAITING_ON_CUSTOMER, + priority: SUPPORT_CASE_PRIORITY.MEDIUM, + category: SUPPORT_CASE_CATEGORY.BILLING, + createdAt: "2025-08-13T16:45:00Z", + updatedAt: "2025-08-14T09:30:00Z", + lastReply: "2025-08-14T09:30:00Z", + description: "Need clarification on charges in recent invoice.", + assignedTo: "Billing Department", + }, + { + id: 12003, + subject: "SSL Certificate Installation", + status: SUPPORT_CASE_STATUS.RESOLVED, + priority: SUPPORT_CASE_PRIORITY.LOW, + category: SUPPORT_CASE_CATEGORY.TECHNICAL, + createdAt: "2025-08-12T08:15:00Z", + updatedAt: "2025-08-12T15:45:00Z", + lastReply: "2025-08-12T15:45:00Z", + description: "Request assistance with SSL certificate installation on shared hosting.", + assignedTo: "Technical Support Team", + }, + { + id: 12004, + subject: "Feature Request: Control Panel Enhancement", + status: SUPPORT_CASE_STATUS.OPEN, + priority: SUPPORT_CASE_PRIORITY.LOW, + category: SUPPORT_CASE_CATEGORY.FEATURE_REQUEST, + createdAt: "2025-08-11T13:20:00Z", + updatedAt: "2025-08-11T13:20:00Z", + description: "Would like to see improved backup management in the control panel.", + assignedTo: "Development Team", + }, + { + id: 12005, + subject: "Server Migration Assistance", + status: SUPPORT_CASE_STATUS.CLOSED, + priority: SUPPORT_CASE_PRIORITY.MEDIUM, + category: SUPPORT_CASE_CATEGORY.TECHNICAL, + createdAt: "2025-08-10T11:00:00Z", + updatedAt: "2025-08-11T17:30:00Z", + lastReply: "2025-08-11T17:30:00Z", + description: "Need help migrating website from old server to new VPS.", + assignedTo: "Migration Team", + }, + ]; + + async listCases(rawFilters?: SupportCaseFilter): Promise { + const filters = supportCaseFilterSchema.parse(rawFilters ?? {}); + const filteredCases = this.applyFilters(this.cases, filters); + const result = { + cases: filteredCases, + summary: this.buildSummary(filteredCases), + }; + return supportCaseListSchema.parse(result); + } + + private applyFilters(cases: SupportCase[], filters: SupportCaseFilter): SupportCase[] { + const search = filters.search?.toLowerCase().trim(); + return cases.filter(supportCase => { + if (filters.status && supportCase.status !== filters.status) { + return false; + } + if (filters.priority && supportCase.priority !== filters.priority) { + return false; + } + if (filters.category && supportCase.category !== filters.category) { + return false; + } + if (search) { + const haystack = `${supportCase.subject} ${supportCase.description} ${supportCase.id}`.toLowerCase(); + if (!haystack.includes(search)) { + return false; + } + } + return true; + }); + } + + private buildSummary(cases: SupportCase[]): SupportCaseList["summary"] { + const open = cases.filter(c => OPEN_STATUSES.includes(c.status)).length; + const highPriority = cases.filter(c => HIGH_PRIORITIES.includes(c.priority)).length; + const resolved = cases.filter(c => RESOLVED_STATUSES.includes(c.status)).length; + + return { + total: cases.length, + open, + highPriority, + resolved, + }; + } +} diff --git a/apps/portal/src/features/billing/components/InvoiceList/InvoiceList.tsx b/apps/portal/src/features/billing/components/InvoiceList/InvoiceList.tsx index 5dd90146..2669ff3b 100644 --- a/apps/portal/src/features/billing/components/InvoiceList/InvoiceList.tsx +++ b/apps/portal/src/features/billing/components/InvoiceList/InvoiceList.tsx @@ -9,7 +9,7 @@ import { PaginationBar } from "@/components/molecules/PaginationBar/PaginationBa import { InvoiceTable } from "@/features/billing/components/InvoiceTable/InvoiceTable"; import { useInvoices } from "@/features/billing/hooks/useBilling"; import { useSubscriptionInvoices } from "@/features/subscriptions/hooks/useSubscriptions"; -import type { Invoice, InvoiceStatus } from "@customer-portal/domain/billing"; +import { INVOICE_STATUS, type Invoice, type InvoiceStatus } from "@customer-portal/domain/billing"; import { cn } from "@/lib/utils"; interface InvoicesListProps { @@ -20,6 +20,8 @@ interface InvoicesListProps { className?: string; } +const INVOICE_STATUS_OPTIONS = Object.values(INVOICE_STATUS) as InvoiceStatus[]; + export function InvoicesList({ subscriptionId, pageSize = 10, @@ -28,7 +30,7 @@ export function InvoicesList({ className, }: InvoicesListProps) { const [searchTerm, setSearchTerm] = useState(""); - const [statusFilter, setStatusFilter] = useState("all"); + const [statusFilter, setStatusFilter] = useState("all"); const [currentPage, setCurrentPage] = useState(1); const isSubscriptionMode = typeof subscriptionId === "number" && !Number.isNaN(subscriptionId); @@ -70,13 +72,16 @@ export function InvoicesList({ }); }, [invoices, searchTerm]); - const statusFilterOptions = [ - { value: "all", label: "All Status" }, - { value: "Unpaid", label: "Unpaid" }, - { value: "Paid", label: "Paid" }, - { value: "Overdue", label: "Overdue" }, - { value: "Cancelled", label: "Cancelled" }, - ]; + const statusFilterOptions = useMemo( + () => [ + { value: "all" as const, label: "All Status" }, + ...INVOICE_STATUS_OPTIONS.map(status => ({ + value: status, + label: status, + })), + ], + [] + ); if (isLoading || error) { return ( @@ -126,7 +131,8 @@ export function InvoicesList({ setStatusFilter(event.target.value)} + onChange={event => + setStatusFilter(event.target.value as SupportCaseStatus | "all") + } className="block w-full px-3 py-2 border border-gray-300 rounded-md leading-5 bg-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500" > - - - - - - + {statusFilterOptions.map(option => ( + + ))} @@ -322,14 +257,16 @@ export function SupportCasesView() {
@@ -339,7 +276,7 @@ export function SupportCasesView() { {/* Cases List */}
    - {filteredCases.map(supportCase => ( + {cases.map(supportCase => (
  • {/* Empty State */} - {filteredCases.length === 0 && ( + {cases.length === 0 && (

    No support cases found

    diff --git a/apps/portal/src/lib/api/index.ts b/apps/portal/src/lib/api/index.ts index 7e3b9937..3dd6b189 100644 --- a/apps/portal/src/lib/api/index.ts +++ b/apps/portal/src/lib/api/index.ts @@ -102,6 +102,9 @@ export const queryKeys = { list: () => ["orders", "list"] as const, detail: (id: string | number) => ["orders", "detail", String(id)] as const, }, + support: { + cases: (params?: Record) => ["support", "cases", params] as const, + }, currency: { default: () => ["currency", "default"] as const, }, diff --git a/docs/orders/ORDER-STATUS-UPDATES-STRATEGY.md b/docs/orders/ORDER-STATUS-UPDATES-STRATEGY.md index 2f6ce757..bf2bc379 100644 --- a/docs/orders/ORDER-STATUS-UPDATES-STRATEGY.md +++ b/docs/orders/ORDER-STATUS-UPDATES-STRATEGY.md @@ -59,7 +59,7 @@ export class OrderEventsService { }); } - publish(orderId: string, update: OrderUpdateEvent): void { + publish(orderId: string, update: OrderUpdateEventPayload): void { const event = this.buildEvent("order.update", update); currentObservers.forEach(observer => observer.next(event)); } @@ -155,4 +155,3 @@ WebSockets provide bidirectional communication but are more complex to implement **Status**: Phase 1 & 2 Complete, Phase 3 (monitoring) in progress **Last Updated**: November 2025 **Owner**: Engineering Team - diff --git a/packages/domain/index.ts b/packages/domain/index.ts index c17c44ef..01c8a70d 100644 --- a/packages/domain/index.ts +++ b/packages/domain/index.ts @@ -8,6 +8,7 @@ export * as Billing from "./billing/index"; export * as Subscriptions from "./subscriptions/index"; export * as Payments from "./payments/index"; export * as Sim from "./sim/index"; +export * as Support from "./support/index"; export * as Orders from "./orders/index"; export * as Catalog from "./catalog/index"; export * as Common from "./common/index"; diff --git a/packages/domain/orders/events.ts b/packages/domain/orders/events.ts new file mode 100644 index 00000000..c267a189 --- /dev/null +++ b/packages/domain/orders/events.ts @@ -0,0 +1,26 @@ +/** + * Order Events - Shared Contracts + * + * Shared event payloads for Server-Sent Events used by both the BFF + * and the frontend. Keeping these definitions in the domain package + * guarantees both layers stay in sync when the payload evolves. + */ + +export type OrderUpdateStage = "started" | "in_progress" | "completed" | "failed"; + +export interface OrderStreamEvent { + event: T; + data: P; +} + +export interface OrderUpdateEventPayload { + orderId: string; + status?: string; + activationStatus?: string | null; + message?: string; + reason?: string; + stage?: OrderUpdateStage; + source?: string; + timestamp: string; + payload?: Record | null; +} diff --git a/packages/domain/orders/index.ts b/packages/domain/orders/index.ts index 78389837..ecc88fd6 100644 --- a/packages/domain/orders/index.ts +++ b/packages/domain/orders/index.ts @@ -40,6 +40,7 @@ export * from "./validation"; // Utilities export * from "./utils"; +export * from "./events"; export { buildSimOrderConfigurations, normalizeBillingCycle, diff --git a/packages/domain/sim/helpers.ts b/packages/domain/sim/helpers.ts new file mode 100644 index 00000000..2deef754 --- /dev/null +++ b/packages/domain/sim/helpers.ts @@ -0,0 +1,18 @@ +import { SIM_STATUS } from "./contract"; +import type { SimStatus } from "./schema"; + +export function canManageActiveSim(status: SimStatus): boolean { + return status === SIM_STATUS.ACTIVE; +} + +export function canReissueEsim(status: SimStatus): boolean { + return canManageActiveSim(status); +} + +export function canCancelSim(status: SimStatus): boolean { + return canManageActiveSim(status); +} + +export function canTopUpSim(status: SimStatus): boolean { + return canManageActiveSim(status); +} diff --git a/packages/domain/sim/index.ts b/packages/domain/sim/index.ts index de2a1f99..15443025 100644 --- a/packages/domain/sim/index.ts +++ b/packages/domain/sim/index.ts @@ -15,6 +15,7 @@ export * from "./lifecycle"; // Validation functions export * from "./validation"; +export * from "./helpers"; // Re-export types for convenience export type { diff --git a/packages/domain/support/contract.ts b/packages/domain/support/contract.ts new file mode 100644 index 00000000..dc6b927c --- /dev/null +++ b/packages/domain/support/contract.ts @@ -0,0 +1,27 @@ +/** + * Support Domain - Contract + * + * Constants for support case statuses, priorities, and categories. + */ + +export const SUPPORT_CASE_STATUS = { + OPEN: "Open", + IN_PROGRESS: "In Progress", + WAITING_ON_CUSTOMER: "Waiting on Customer", + RESOLVED: "Resolved", + CLOSED: "Closed", +} as const; + +export const SUPPORT_CASE_PRIORITY = { + LOW: "Low", + MEDIUM: "Medium", + HIGH: "High", + CRITICAL: "Critical", +} as const; + +export const SUPPORT_CASE_CATEGORY = { + TECHNICAL: "Technical", + BILLING: "Billing", + GENERAL: "General", + FEATURE_REQUEST: "Feature Request", +} as const; diff --git a/packages/domain/support/index.ts b/packages/domain/support/index.ts new file mode 100644 index 00000000..3af533f8 --- /dev/null +++ b/packages/domain/support/index.ts @@ -0,0 +1,7 @@ +export { + SUPPORT_CASE_STATUS, + SUPPORT_CASE_PRIORITY, + SUPPORT_CASE_CATEGORY, +} from "./contract"; + +export * from "./schema"; diff --git a/packages/domain/support/schema.ts b/packages/domain/support/schema.ts new file mode 100644 index 00000000..11dcfea6 --- /dev/null +++ b/packages/domain/support/schema.ts @@ -0,0 +1,74 @@ +import { z } from "zod"; +import { + SUPPORT_CASE_STATUS, + SUPPORT_CASE_PRIORITY, + SUPPORT_CASE_CATEGORY, +} from "./contract"; + +const supportCaseStatusValues = [ + SUPPORT_CASE_STATUS.OPEN, + SUPPORT_CASE_STATUS.IN_PROGRESS, + SUPPORT_CASE_STATUS.WAITING_ON_CUSTOMER, + SUPPORT_CASE_STATUS.RESOLVED, + SUPPORT_CASE_STATUS.CLOSED, +] as const; + +const supportCasePriorityValues = [ + SUPPORT_CASE_PRIORITY.LOW, + SUPPORT_CASE_PRIORITY.MEDIUM, + SUPPORT_CASE_PRIORITY.HIGH, + SUPPORT_CASE_PRIORITY.CRITICAL, +] as const; + +const supportCaseCategoryValues = [ + SUPPORT_CASE_CATEGORY.TECHNICAL, + SUPPORT_CASE_CATEGORY.BILLING, + SUPPORT_CASE_CATEGORY.GENERAL, + SUPPORT_CASE_CATEGORY.FEATURE_REQUEST, +] as const; + +export const supportCaseStatusSchema = z.enum(supportCaseStatusValues); +export const supportCasePrioritySchema = z.enum(supportCasePriorityValues); +export const supportCaseCategorySchema = z.enum(supportCaseCategoryValues); + +export const supportCaseSchema = z.object({ + id: z.number().int().positive(), + subject: z.string().min(1), + status: supportCaseStatusSchema, + priority: supportCasePrioritySchema, + category: supportCaseCategorySchema, + createdAt: z.string(), + updatedAt: z.string(), + lastReply: z.string().optional(), + description: z.string(), + assignedTo: z.string().optional(), +}); + +export const supportCaseSummarySchema = z.object({ + total: z.number().int().nonnegative(), + open: z.number().int().nonnegative(), + highPriority: z.number().int().nonnegative(), + resolved: z.number().int().nonnegative(), +}); + +export const supportCaseListSchema = z.object({ + cases: z.array(supportCaseSchema), + summary: supportCaseSummarySchema, +}); + +export const supportCaseFilterSchema = z + .object({ + status: supportCaseStatusSchema.optional(), + priority: supportCasePrioritySchema.optional(), + category: supportCaseCategorySchema.optional(), + search: z.string().trim().min(1).optional(), + }) + .default({}); + +export type SupportCaseStatus = z.infer; +export type SupportCasePriority = z.infer; +export type SupportCaseCategory = z.infer; +export type SupportCase = z.infer; +export type SupportCaseSummary = z.infer; +export type SupportCaseList = z.infer; +export type SupportCaseFilter = z.infer;