From 7dd6b1ede68b610e51e92ba5f2bf87fb98f81ad8 Mon Sep 17 00:00:00 2001 From: Isaac Way Date: Wed, 21 Dec 2022 19:18:44 -0600 Subject: [PATCH 1/2] fixes issue with headers --- packages/trpc-panel/src/react-app/Root.tsx | 14 ++-- .../src/react-app/components/HeadersPopup.tsx | 2 +- .../components/contexts/HeadersContext.tsx | 44 +++++++---- .../src/react-app/components/form/Field.tsx | 46 ++++++++++-- .../components/form/ProcedureForm/index.tsx | 73 +++++++++++++------ .../components/form/fields/ArrayField.tsx | 11 ++- .../components/form/fields/BooleanField.tsx | 4 +- .../form/fields/DiscriminatedUnionField.tsx | 7 +- .../components/form/fields/EnumField.tsx | 40 +++++----- .../components/form/fields/NumberField.tsx | 4 +- .../components/form/fields/ObjectField.tsx | 6 +- .../components/form/fields/TextField.tsx | 4 +- 12 files changed, 174 insertions(+), 81 deletions(-) diff --git a/packages/trpc-panel/src/react-app/Root.tsx b/packages/trpc-panel/src/react-app/Root.tsx index d8cd997..ea80e7f 100644 --- a/packages/trpc-panel/src/react-app/Root.tsx +++ b/packages/trpc-panel/src/react-app/Root.tsx @@ -6,6 +6,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { createTRPCReact, httpBatchLink } from "@trpc/react-query"; import { HeadersContext, + HeadersContextProvider, useHeaders, } from "@src/react-app/components/contexts/HeadersContext"; import { HeadersPopup } from "@src/react-app/components/HeadersPopup"; @@ -27,11 +28,10 @@ export function RootComponent({ options: RenderOptions; trpc: ReturnType; }) { - const headers = useHeaders(); return ( - - - + + + @@ -41,9 +41,9 @@ export function RootComponent({ - - - + + + ); } diff --git a/packages/trpc-panel/src/react-app/components/HeadersPopup.tsx b/packages/trpc-panel/src/react-app/components/HeadersPopup.tsx index d4027ed..fb4fbfa 100644 --- a/packages/trpc-panel/src/react-app/components/HeadersPopup.tsx +++ b/packages/trpc-panel/src/react-app/components/HeadersPopup.tsx @@ -94,7 +94,7 @@ export function HeadersPopup() {
{headers.map(([headerKey, headerValue], i) => ( -
+
{ - if (!val) localStorage.removeItem(headersLocalStorageKey); - setSaveHeadersToLocalStorage(val); - }, - }; + return ( + { + if (!val) localStorage.removeItem(headersLocalStorageKey); + setSaveHeadersToLocalStorage(val); + }, + }} + > + {children} + + ); +} + +export function useHeaders(): HeadersContextType { + const ctx = useContext(HeadersContext); + if (!ctx) throw new Error("No headers context."); + return ctx; } export function useHeadersContext() { diff --git a/packages/trpc-panel/src/react-app/components/form/Field.tsx b/packages/trpc-panel/src/react-app/components/form/Field.tsx index 847f866..1e68c25 100644 --- a/packages/trpc-panel/src/react-app/components/form/Field.tsx +++ b/packages/trpc-panel/src/react-app/components/form/Field.tsx @@ -1,3 +1,4 @@ +import { ROOT_VALS_PROPERTY_NAME } from "@src/react-app/components/form/ProcedureForm"; import React from "react"; import { Control } from "react-hook-form"; import { ParsedInputNode } from "../../../parse/parseNodeTypes"; @@ -15,32 +16,63 @@ export function Field({ control, }: { inputNode: ParsedInputNode; - control: Control; + control: Control; }) { - const path = inputNode.path.join("."); + const label = inputNode.path.join("."); + const path = `${ROOT_VALS_PROPERTY_NAME}.${label}`; switch (inputNode.type) { case "string": - return ; + return ( + + ); case "number": - return ; + return ( + + ); case "object": - return ; + return ; case "boolean": - return ; + return ( + + ); case "enum": return ( ); case "array": - return ; + return ( + + ); case "discriminated-union": return ( diff --git a/packages/trpc-panel/src/react-app/components/form/ProcedureForm/index.tsx b/packages/trpc-panel/src/react-app/components/form/ProcedureForm/index.tsx index 5401874..4c158c0 100644 --- a/packages/trpc-panel/src/react-app/components/form/ProcedureForm/index.tsx +++ b/packages/trpc-panel/src/react-app/components/form/ProcedureForm/index.tsx @@ -17,6 +17,7 @@ import { ObjectField } from "@src/react-app/components/form/fields/ObjectField"; import { fullFormats } from "ajv-formats/dist/formats"; import type { ParsedInputNode } from "@src/parse/parseNodeTypes"; import { DocumentationSection } from "@src/react-app/components/form/ProcedureForm/DescriptionSection"; +import { Field } from "@src/react-app/components/form/Field"; const TRPCErrorSchema = z.object({ shape: z.object({ @@ -36,6 +37,8 @@ function isTrpcError(error: unknown): error is TRPCErrorType { return parse.success; } +export const ROOT_VALS_PROPERTY_NAME = "vals"; + export function ProcedureForm({ procedure, name, @@ -91,18 +94,24 @@ export function ProcedureForm({ reset: resetForm, handleSubmit, } = useForm({ - resolver: ajvResolver(procedure.inputSchema as any, { + resolver: ajvResolver(wrapJsonSchema(procedure.inputSchema as any), { formats: fullFormats, }), - defaultValues: defaultFormValuesForNode(procedure.node), + defaultValues: { + [ROOT_VALS_PROPERTY_NAME]: defaultFormValuesForNode(procedure.node), + }, }); - function onSubmit(data: any) { + function onSubmit(data: { [ROOT_VALS_PROPERTY_NAME]: any }) { if (procedure.procedureType === "query") { - setQueryInput({ ...data }); - invalidateQuery(data); + const newData = { ...data }; + setQueryInput(newData[ROOT_VALS_PROPERTY_NAME]); + invalidateQuery(data.vals); } else { - mutation.mutateAsync(data).then(setMutationResponse).catch(); + mutation + .mutateAsync(data[ROOT_VALS_PROPERTY_NAME]) + .then(setMutationResponse) + .catch(); } } @@ -127,6 +136,9 @@ export function ProcedureForm({ procedure.procedureType === "query" ? query.data : mutationResponse; const error = procedure.procedureType == "query" ? query.error : mutation.error; + + const fieldName = procedure.node.path.join("."); + return (
- {procedure.node.type == "object" && ( - } - > - {Object.keys(procedure.node.children).length > 0 && ( + + } + > + {procedure.node.type === "object" ? ( + Object.keys(procedure.node.children).length > 0 && ( - )} - - - - )} + ) + ) : ( + + )} + + +
@@ -211,3 +226,17 @@ function XButton({
); } + +function wrapJsonSchema(jsonSchema: any) { + delete jsonSchema["$schema"]; + + return { + type: "object", + properties: { + [ROOT_VALS_PROPERTY_NAME]: jsonSchema, + }, + required: [ROOT_VALS_PROPERTY_NAME], + additionalProperties: false, + $schema: "http://json-schema.org/draft-07/schema#", + }; +} diff --git a/packages/trpc-panel/src/react-app/components/form/fields/ArrayField.tsx b/packages/trpc-panel/src/react-app/components/form/fields/ArrayField.tsx index ff8e887..f128c12 100644 --- a/packages/trpc-panel/src/react-app/components/form/fields/ArrayField.tsx +++ b/packages/trpc-panel/src/react-app/components/form/fields/ArrayField.tsx @@ -8,15 +8,18 @@ import { InputGroupContainer } from "@src/react-app/components/InputGroupContain import DataArray from "@mui/icons-material/DataArray"; import { AddItemButton } from "@src/react-app/components/AddItemButton"; import { FieldError } from "@src/react-app/components/form/fields/FieldError"; +import { ROOT_VALS_PROPERTY_NAME } from "@src/react-app/components/form/ProcedureForm"; var currentKeyCount = 0; export function ArrayField({ name, + label, control, node, }: { name: string; + label: string; control: Control; node: ParsedInputNode & { type: "array" }; }) { @@ -35,7 +38,9 @@ export function ArrayField({ function getValueFromWatch() { var r = watch; - for (var p of node.path) { + for (var p of [ROOT_VALS_PROPERTY_NAME].concat( + node.path.map((e) => e + "") + )) { r = r[p]; } return r; @@ -59,10 +64,10 @@ export function ArrayField({ return ( } - title={name} + title={label} > {field.value.map((_: ParsedInputNode, i: number) => ( - + ; node: ParsedInputNode; }) { @@ -17,7 +19,7 @@ export function BooleanField({ return ( ; node: ParsedInputNode; }) { @@ -39,7 +41,7 @@ export function DiscriminatedUnionField({ ]! as ParsedInputNode & { type: "object" }; return ( } > } - // IDK if this needs a name - name={``} + label={``} /> {fieldState.error?.message && ( ; - options: string[]; + name: string; + label: string; + control: Control; + options: string[]; }) { - const { field, fieldState } = useController({ - name, - control, - }); - return ( - - ); + const { field, fieldState } = useController({ + name, + control, + }); + return ( + + ); } diff --git a/packages/trpc-panel/src/react-app/components/form/fields/NumberField.tsx b/packages/trpc-panel/src/react-app/components/form/fields/NumberField.tsx index 6f0d190..f3bb741 100644 --- a/packages/trpc-panel/src/react-app/components/form/fields/NumberField.tsx +++ b/packages/trpc-panel/src/react-app/components/form/fields/NumberField.tsx @@ -6,9 +6,11 @@ import type { ParsedInputNode } from "@src/parse/parseNodeTypes"; export function NumberField({ name, control, + label, node: inputNode, }: { name: string; + label: string; control: Control; node: ParsedInputNode; }) { @@ -44,7 +46,7 @@ export function NumberField({ onChange={onChange} value={stringValue} errorMessage={fieldState.error?.message} - label={name} + label={label} fieldId={inputNode.path.join(".")} inputProps={{ inputMode: "decimal" }} /> diff --git a/packages/trpc-panel/src/react-app/components/form/fields/ObjectField.tsx b/packages/trpc-panel/src/react-app/components/form/fields/ObjectField.tsx index 1475d87..4e5e99f 100644 --- a/packages/trpc-panel/src/react-app/components/form/fields/ObjectField.tsx +++ b/packages/trpc-panel/src/react-app/components/form/fields/ObjectField.tsx @@ -6,13 +6,13 @@ import ObjectIcon from "@mui/icons-material/DataObjectOutlined"; import { InputGroupContainer } from "../../InputGroupContainer"; export function ObjectField({ - name, + label, control, node, topLevel, overrideIconElement, }: { - name: string; + label: string; control: Control; node: ParsedInputNode & { type: "object" }; topLevel?: boolean; @@ -29,7 +29,7 @@ export function ObjectField({ } return ( } > {Object.entries(node.children).map(([childFieldName, e]) => ( diff --git a/packages/trpc-panel/src/react-app/components/form/fields/TextField.tsx b/packages/trpc-panel/src/react-app/components/form/fields/TextField.tsx index 932667f..f85a861 100644 --- a/packages/trpc-panel/src/react-app/components/form/fields/TextField.tsx +++ b/packages/trpc-panel/src/react-app/components/form/fields/TextField.tsx @@ -5,10 +5,12 @@ import type { ParsedInputNode } from "@src/parse/parseNodeTypes"; export function TextField({ name, + label, control, node: inputNode, }: { name: string; + label: string; control: Control; node: ParsedInputNode; }) { @@ -22,7 +24,7 @@ export function TextField({ value={field.value ? field.value : ""} onChange={field.onChange} errorMessage={fieldState.error?.message} - label={`${name}${inputNode.optional ? "" : "*"}`} + label={`${label}${inputNode.optional ? "" : "*"}`} fieldId={inputNode.path.join(".")} /> ); From f3e53e3a96add07f08c39a5104aa85cd002eeb37 Mon Sep 17 00:00:00 2001 From: Isaac Way Date: Wed, 21 Dec 2022 19:20:27 -0600 Subject: [PATCH 2/2] add test app field --- packages/test-app/src/router.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/test-app/src/router.ts b/packages/test-app/src/router.ts index 3083d66..c9b5ed6 100644 --- a/packages/test-app/src/router.ts +++ b/packages/test-app/src/router.ts @@ -17,6 +17,8 @@ const t = initTRPC async function createContext(opts: trpcExpress.CreateExpressContextOptions) { const authHeader = opts.req.headers["authorization"]; + console.log("Request headers: "); + console.log(authHeader); return { authorized: !!authHeader, }; @@ -265,10 +267,7 @@ export const testRouter = t.router({ .query(({ input }) => { return { greeting: `Hello ${input.name}!` }; }), - sayHello2: t.procedure - .meta({ /* 👉 */ description: "This shows in the panel." }) - .input(z.object({ name: z.string() })) - .query(({ input }) => { - return { greeting: `Hello ${input.name}!` }; - }), + nonObjectInput: t.procedure.input(z.string()).query(({ input }) => { + return `Your input was ${input}`; + }), });