From c0ff0630237f59d95056bf42aeaa4c320335218e Mon Sep 17 00:00:00 2001 From: josh Date: Tue, 21 Apr 2026 20:37:23 -0400 Subject: [PATCH] Type mutation inputs with shared Zod schemas instead of Record Replaced loose Record types on useCreateTicket, useUpdateTicket, useCreateUser, useUpdateUser, useUpdateWebhook, and useCreateSavedView with their corresponding shared schema types. Fixed three type errors this surfaced at call sites. Co-Authored-By: Claude Opus 4.6 --- client/src/api/queries.ts | 16 ++++++++++------ client/src/pages/NewTicket.tsx | 7 ++++--- client/src/pages/Tickets.tsx | 2 +- client/src/pages/admin/Users.tsx | 10 +++++----- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/client/src/api/queries.ts b/client/src/api/queries.ts index ebe24cc..be3bfd4 100644 --- a/client/src/api/queries.ts +++ b/client/src/api/queries.ts @@ -7,6 +7,10 @@ import type { Webhook, PaginatedResponse, } from '../../../shared/types'; +import type { CreateTicketInput, UpdateTicketInput } from '../../../shared/schemas/ticket'; +import type { CreateUserInput, UpdateUserInput } from '../../../shared/schemas/user'; +import type { UpdateWebhookInput } from '../../../shared/schemas/notification'; +import type { CreateSavedViewInput } from '../../../shared/schemas/savedView'; // ── Keys ───────────────────────────────────────────────────────────────────── @@ -96,7 +100,7 @@ export function useTicketAudit(id: string | undefined, enabled = true) { export function useCreateTicket() { const qc = useQueryClient(); return useMutation({ - mutationFn: async (data: Record) => + mutationFn: async (data: CreateTicketInput) => (await api.post('/tickets', data)).data, onSuccess: () => { qc.invalidateQueries({ queryKey: ['tickets'] }); @@ -107,7 +111,7 @@ export function useCreateTicket() { export function useUpdateTicket() { const qc = useQueryClient(); return useMutation({ - mutationFn: async ({ id, data }: { id: string; data: Record }) => + mutationFn: async ({ id, data }: { id: string; data: UpdateTicketInput }) => (await api.patch(`/tickets/${id}`, data)).data, onSuccess: (ticket) => { qc.setQueryData(qk.ticket(ticket.displayId), ticket); @@ -265,7 +269,7 @@ export function useUsers() { export function useCreateUser() { const qc = useQueryClient(); return useMutation({ - mutationFn: async (data: Record) => + mutationFn: async (data: CreateUserInput) => (await api.post('/users', data)).data, onSuccess: () => qc.invalidateQueries({ queryKey: qk.users() }), }); @@ -274,7 +278,7 @@ export function useCreateUser() { export function useUpdateUser() { const qc = useQueryClient(); return useMutation({ - mutationFn: async ({ id, data }: { id: string; data: Record }) => + mutationFn: async ({ id, data }: { id: string; data: UpdateUserInput }) => (await api.patch(`/users/${id}`, data)).data, onSuccess: () => qc.invalidateQueries({ queryKey: qk.users() }), }); @@ -344,7 +348,7 @@ export function useCreateWebhook() { export function useUpdateWebhook() { const qc = useQueryClient(); return useMutation({ - mutationFn: async ({ id, data }: { id: string; data: Record }) => + mutationFn: async ({ id, data }: { id: string; data: UpdateWebhookInput }) => (await api.patch(`/webhooks/${id}`, data)).data, onSuccess: () => qc.invalidateQueries({ queryKey: qk.webhooks() }), }); @@ -382,7 +386,7 @@ export function useSavedViews() { export function useCreateSavedView() { const qc = useQueryClient(); return useMutation({ - mutationFn: async (data: { name: string; filters: Record }) => + mutationFn: async (data: CreateSavedViewInput) => (await api.post('/saved-views', data)).data, onSuccess: () => qc.invalidateQueries({ queryKey: qk.savedViews() }), }); diff --git a/client/src/pages/NewTicket.tsx b/client/src/pages/NewTicket.tsx index be1e9c6..5525211 100644 --- a/client/src/pages/NewTicket.tsx +++ b/client/src/pages/NewTicket.tsx @@ -38,9 +38,10 @@ export default function NewTicketModal({ onClose }: NewTicketModalProps) { const onSubmit = async (data: CreateTicketInput) => { setError(''); try { - const payload: Record = { ...data }; - if (!data.assigneeId) delete payload.assigneeId; - const created = await createTicket.mutateAsync(payload); + const { assigneeId, ...rest } = data; + const created = await createTicket.mutateAsync( + assigneeId ? { ...rest, assigneeId } : rest, + ); onClose(); navigate(`/${created.displayId}`); } catch { diff --git a/client/src/pages/Tickets.tsx b/client/src/pages/Tickets.tsx index f85f18b..4d662d3 100644 --- a/client/src/pages/Tickets.tsx +++ b/client/src/pages/Tickets.tsx @@ -154,7 +154,7 @@ export default function Tickets() { const currentFilters = useMemo( () => ({ status: status || undefined, - severity: severity || undefined, + severity: severity ? Number(severity) : undefined, assigneeId: assigneeId || undefined, categoryId: categoryId || undefined, typeId: typeId || undefined, diff --git a/client/src/pages/admin/Users.tsx b/client/src/pages/admin/Users.tsx index 54b1f9e..3bfa46e 100644 --- a/client/src/pages/admin/Users.tsx +++ b/client/src/pages/admin/Users.tsx @@ -4,6 +4,7 @@ import { toast } from 'sonner'; import Layout from '../../components/Layout'; import Modal from '../../components/Modal'; import { User, Role } from '../../types'; +import type { CreateUserInput } from '../../../../shared/schemas/user'; import { useAuth } from '../../contexts/AuthContext'; import { useUsers, @@ -105,14 +106,13 @@ export default function AdminUsers() { e.preventDefault(); setError(''); try { - const payload: Record = { + const created = await createUser.mutateAsync({ username: form.username, email: form.email, displayName: form.displayName, - role: form.role, - }; - if (form.password) payload.password = form.password; - const created = await createUser.mutateAsync(payload); + role: form.role as CreateUserInput['role'], + password: form.password, + }); if (created.apiKey) setNewApiKey(created.apiKey); else closeModal(); } catch (err: unknown) {