test setup lackin

This commit is contained in:
2025-09-03 22:04:56 +02:00
parent 869cc07fdd
commit 276c3dd75f
35 changed files with 629 additions and 285 deletions

View File

@@ -1,11 +1,15 @@
import { DeleteIcon } from "lucide-react";
import type { UseTRPCMutationOptions, UseTRPCMutationResult } from "node_modules/@trpc/react-query/dist/getQueryKey.d-CruH3ncI.mjs";
import { Button } from "~/components/ui/button";
import { useFormMutationContext } from "./MutationProvider";
export default function DeleteButton<TD, TE, TV, TC>(params: { mutation: UseTRPCMutationResult<TD, TE, TV, TC>, id?: string }) {
export default function DeleteButton(params: { id?: string }) {
const ctx = useFormMutationContext()
if (ctx == undefined) {
throw new Error('Dependent form message needs populates form mutations context')
}
if (params.id) {
return (
<Button variant='destructive' onClick={() => params.mutation.mutate(({ id: params.id } as TV))}>
<Button variant='destructive' onClick={() => ctx.deleteMutation.mutate(({ id: params.id ? params.id : "" }))}>
<DeleteIcon />
</Button>
)

View File

@@ -4,22 +4,16 @@ import { DependentFormMessaage, SubmitButton, DeleteButton } from '~/app/_compon
import type { FieldValues, SubmitHandler, UseFormReturn } from 'react-hook-form';
import type { ReactNode } from 'react';
import DependentText from '../../DependentText';
import type { UseTRPCMutationResult } from 'node_modules/@trpc/react-query/dist/getQueryKey.d-CruH3ncI.mjs';
interface Error {
message: string
}
export default function FormScaffold<T extends FieldValues, TD, TE extends Error, TV, TC, TTD, TTE extends Error, TTV, TTC, TTTD, TTTE extends Error, TTTV, TTTC>(params: {
export default function FormScaffold<T extends FieldValues,>(params: {
form: UseFormReturn<T>,
onSubmit: SubmitHandler<T>,
createMutation: UseTRPCMutationResult<TD, TE, TV, TC>,
updateMutation: UseTRPCMutationResult<TTD, TTE, TTV, TTC>,
deleteMutation: UseTRPCMutationResult<TTTD, TTTE, TTTV, TTTC>,
title: string,
children: ReactNode,
children?: ReactNode,
id?: string,
className?: string
}) {
const { form, onSubmit, createMutation, deleteMutation, updateMutation, title, id, className, children } = params
const { form, onSubmit, title, id, className, children } = params
return (
<Card.Card className={className ? className : "w-5/6 lg:w-1/2"}>
<Card.CardHeader>
@@ -32,12 +26,13 @@ export default function FormScaffold<T extends FieldValues, TD, TE extends Error
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8"
data-testid="form"
>
{children}
<SubmitButton id={id} />
<SubmitButton id={id} />
<div className='flex flex-row justify-between'>
<DependentFormMessaage falseStatus={createMutation.status} trueStatus={updateMutation.status} falseError={createMutation.error} trueError={updateMutation.error} bool={id ? true : false} />
<DeleteButton mutation={deleteMutation} id={id} />
<DependentFormMessaage bool={id ? true : false} />
<DeleteButton id={id} />
</div>
</form>
</Form>

View File

@@ -1,19 +1,20 @@
import { FormMessage } from "~/components/ui/form";
import { useFormMutationContext } from "./MutationProvider";
interface Error {
message: string
}
export default function DependentFormMessaage( params: {trueStatus: string, falseStatus: string, falseError?: Error|null, trueError?:Error|null, bool: boolean} ) {
export default function DependentFormMessaage( params: {bool: boolean} ) {
const ctx = useFormMutationContext()
if (ctx == undefined) {
throw new Error('Dependent form message needs populates form mutations context')
}
return (
<>
{
params.bool ?
<FormMessage className={params.trueStatus == "success" ? "text-green-500" : "text-red-500"}>
{params.trueError ? params.trueError.message : params.trueStatus}
<FormMessage className={ctx.updateMutation.status == "success" ? "text-green-500" : "text-red-500"}>
{ctx.updateMutation.error ? ctx.updateMutation.error.message : ctx.updateMutation.status}
</FormMessage> :
<FormMessage className={params.falseStatus == "success" ? "text-green-500" : "text-red-500"}>
{params.falseError ? params.falseError.message : params.falseStatus}
<FormMessage className={ctx.createMutation.status == "success" ? "text-green-500" : "text-red-500"}>
{ctx.createMutation.error ? ctx.createMutation.error.message : ctx.createMutation.status}
</FormMessage>
}
</>

View File

@@ -0,0 +1,33 @@
import type { UseTRPCMutationResult } from "node_modules/@trpc/react-query/dist/getQueryKey.d-CruH3ncI.mjs";
import { createContext, useContext, type ReactNode } from "react";
interface ToString {
toString: () => string
message: string
}
export interface MutationInterface {
mutate: (params:{id:string}) => void
error: ToString | null
status: "error" | "idle" | "pending" | "success"
}
type FormMutationContext = {
createMutation: MutationInterface,
updateMutation: MutationInterface,
deleteMutation: MutationInterface
}
const FormMutationContext = createContext<FormMutationContext|undefined>(undefined)
export function useFormMutationContext() {
return useContext(FormMutationContext)
}
export function FormMutationContextProvider(params: {children: ReactNode, value: FormMutationContext}) {
return (
<FormMutationContext.Provider value={params.value}>
{params.children}
</FormMutationContext.Provider>
)
}

View File

@@ -3,7 +3,7 @@ import DependentText from "../../DependentText";
export default function SubmitButton(params: {id?:string}) {
return (
<Button type="submit">
<Button submit={true}>
<DependentText bool={params.id?true:false} true='Update' false='Create' />
</Button>
)

View File

@@ -21,10 +21,8 @@ function InnerMultiBooleanFormField(params: { options: string[], onChange: (arg0
}
const onCheckedItemChange = (key: string) => {
return function onCheckedChange(checked: CheckedState) {
console.log(key,checked)
let state = context.checkedValues;
if (checked === "indeterminate") {
console.log('checked was intermediate')
return
} else {
state[key] = checked
@@ -36,7 +34,6 @@ function InnerMultiBooleanFormField(params: { options: string[], onChange: (arg0
stateArr.push(key)
}
}
console.log('calling field on change with:', stateArr)
params.onChange(stateArr)
}
}
@@ -53,7 +50,7 @@ function InnerMultiBooleanFormField(params: { options: string[], onChange: (arg0
<FormItem key={opt}>
<div className="flex flex-row justify-between py-2 border-b-1">
<FormLabel>{opt}</FormLabel>
<Checkbox checked={checked(opt)} onCheckedChange={onCheckedItemChange(opt)} />
<Checkbox data-testid="multiboolean-checkbox" checked={checked(opt)} onCheckedChange={onCheckedItemChange(opt)} />
</div>
</FormItem>
))
@@ -71,14 +68,14 @@ export default function MultiBooleanFormField<T extends FieldValues>(params: { c
render={({ field }) => (
<FormItem>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<PopoverTrigger data-testid="multiboolean-popover-trigger" asChild>
<Button variant={'ghost'}>
<FormLabel>{params.label}</FormLabel>
<ChevronDownIcon/>
</Button>
</PopoverTrigger>
<FormControl>
<PopoverContent>
<PopoverContent data-testid="multiboolean-content">
<MultiBooleanFieldContext.Provider value={{checkedValues: checkedValues, setCheckedValue: setCheckedValues}}>
<InnerMultiBooleanFormField onChange={field.onChange} options={params.options} />
</MultiBooleanFieldContext.Provider>

View File

@@ -0,0 +1,19 @@
import TestingTest from "./TestingTest";
import { test } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import { FormMutationContextProvider } from "../Form/Components/MutationProvider";
test('TestingTest', async () => {
const user = userEvent.setup()
render(
<TestingTest/>
)
const submitButton = screen.getByTestId('form-submit')
screen.debug()
user.click(submitButton)
await waitFor(() => {
screen.getByTestId('submitted')
})
screen.debug()
})

View File

@@ -0,0 +1,33 @@
import { useForm } from "react-hook-form";
import { FormScaffold } from "../Form/Components";
import z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react";
import { FormMutationContextProvider } from "../Form/Components/MutationProvider";
export default function TestingTest() {
const schema = z.object({ text: z.string() })
const [submitted, setSubmitted] = useState(false)
const fakeMutation = {
error: {message: "test"},
status: "idle" as const,
mutate: (_:{id:string}) => {}
}
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: {
text: ""
}
})
const onSubmit = (_: z.infer<typeof schema>) => {
setSubmitted(true)
}
return (
<>
<div data-testid={submitted ? "submitted" : "unsubmitted"} />
<FormMutationContextProvider value={{ createMutation: fakeMutation, updateMutation: fakeMutation, deleteMutation: fakeMutation }}>
<FormScaffold form={form} onSubmit={onSubmit} title="Testing Test" />
</FormMutationContextProvider>
</>
)
}

View File

@@ -0,0 +1,13 @@
export default function getBaseUrl() {
if (typeof window !== 'undefined')
// browser should use relative path
return '';
if (process.env.VERCEL_URL)
// reference for vercel.com
return `https://${process.env.VERCEL_URL}`;
if (process.env.RENDER_INTERNAL_HOSTNAME)
// reference for render.com
return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`;
// assume localhost
return `http://localhost:${process.env.PORT ?? 3000}`;
}

View File

@@ -4,21 +4,7 @@ import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experime
import { httpBatchLink } from "@trpc/client";
import React, { useState } from "react"
import { trpc } from "./Client";
function getBaseUrl() {
if (typeof window !== 'undefined')
// browser should use relative path
return '';
if (process.env.VERCEL_URL)
// reference for vercel.com
return `https://${process.env.VERCEL_URL}`;
if (process.env.RENDER_INTERNAL_HOSTNAME)
// reference for render.com
return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`;
// assume localhost
return `http://localhost:${process.env.PORT ?? 3000}`;
}
import getBaseUrl from "~/app/_trpc/GetBaseUrl";
let clientQueryClient: QueryClient | undefined = undefined;
const makeQueryClient = () => new QueryClient({
defaultOptions: {
@@ -38,12 +24,15 @@ function getQueryClient() {
}
}
export default function TrpcProvider({ children }: { children: React.ReactNode }) {
console.log("provider calls get base url")
const baseUrl = getBaseUrl()
console.log("provider got baseurl:", baseUrl)
const queryClient = getQueryClient();
const [trpcClient] = useState(() => {
return trpc.createClient({
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
url: `${baseUrl}/api/trpc`,
}),
],
})

View File

@@ -11,6 +11,7 @@ import { useState } from 'react';
import { SelectFormField, TextInputFormField } from '~/app/_components/Form/Fields';
import { usePathname, useRouter } from 'next/navigation';
import { SelectItem } from '~/components/ui/select';
import FormMutationContextProvider from '~/app/_components/Form/Components/MutationProvider';
export default function CreateUpdateCvCategoryForm(params: { className?: string, entity?: IterableElement<RouterOutputs['category']['select']> }) {
const schemas = entitySchemas('cvCategory')
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
@@ -33,22 +34,25 @@ export default function CreateUpdateCvCategoryForm(params: { className?: string,
createMutation.mutate(values)
}
return (
<FormScaffold
form={form}
createMutation={createMutation}
updateMutation={updateMutation}
deleteMutation={deleteMutation}
onSubmit={onSubmit}
title='Category'
id={id}
className={params.className}
>
<TextInputFormField control={form.control} name='name' label='Name' />
<SelectFormField control={form.control} name='layoutPosition' label='Layout Position' placeholder={form.getValues().layoutPosition == null ? undefined : form.getValues().layoutPosition}>
{schemas.insert.shape.layoutPosition.unwrap().unwrap().options.map((o) => (
<SelectItem key={o} value={o}> {o} </SelectItem>
))}
</SelectFormField>
</FormScaffold>
<FormMutationContextProvider value={{
createMutation:createMutation,
updateMutation:updateMutation,
deleteMutation:deleteMutation
}}>
<FormScaffold
form={form}
onSubmit={onSubmit}
title='Category'
id={id}
className={params.className}
>
<TextInputFormField control={form.control} name='name' label='Name' />
<SelectFormField control={form.control} name='layoutPosition' label='Layout Position' placeholder={form.getValues().layoutPosition == null ? undefined : form.getValues().layoutPosition}>
{schemas.insert.shape.layoutPosition.unwrap().unwrap().options.map((o) => (
<SelectItem key={o} value={o}> {o} </SelectItem>
))}
</SelectFormField>
</FormScaffold>
</FormMutationContextProvider>
)
}

View File

@@ -13,6 +13,7 @@ import { FormScaffold } from '~/app/_components/Form/Components';
import { useState } from 'react';
import { SelectFormField, TextInputFormField, MdeFormField, CalenderFormField, BooleanFormField } from '~/app/_components/Form/Fields'
import { usePathname, useRouter } from 'next/navigation';
import {FormMutationContextProvider, type FormCreateMutationInterface} from '~/app/_components/Form/Components/MutationProvider';
export default function CreateUpdateCvEntryForm(params: { className?: string, entity?: IterableElement<RouterOutputs['entry']['select']>, isUpdate?: boolean }) {
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
const { theme } = useTheme()
@@ -42,11 +43,16 @@ export default function CreateUpdateCvEntryForm(params: { className?: string, en
createMutation.mutate(values)
}
return (
//todo
// solved by: strictFunctionTypes: false
// sadge
<FormMutationContextProvider value={{
createMutation:createMutation,
updateMutation:updateMutation,
deleteMutation:deleteMutation
}}>
<FormScaffold
form={form}
createMutation={createMutation}
updateMutation={updateMutation}
deleteMutation={deleteMutation}
onSubmit={onSubmit}
title='Entry'
id={id}
@@ -56,7 +62,7 @@ export default function CreateUpdateCvEntryForm(params: { className?: string, en
{
categoriesSuccess ?
<>
{categories.map((c) => {
{categories?.map((c) => {
return (<SelectItem key={c.id} value={c.id}> {c.name} </SelectItem>)
})}
</> :
@@ -69,5 +75,6 @@ export default function CreateUpdateCvEntryForm(params: { className?: string, en
<CalenderFormField control={form.control} name='toTime' label='To Date' />
<BooleanFormField control={form.control} name='hideDates' label='Hide Dates' />
</FormScaffold>
</FormMutationContextProvider>
)
}

View File

@@ -1,8 +1,7 @@
'use server'
import { expect, test } from 'vitest'
import { render, screen } from '@testing-library/react'
import CreateUpdateProjectForm from './CreateUpdateProjectForm'
import TrpcProvider from '~/../test/trpc/vitest.trpcProvider.mock'
import TrpcProvider from '~/app/_trpc/TrpcProvider'
test('CreateUpdateProjectForm', () => {
render(
@@ -10,6 +9,4 @@ test('CreateUpdateProjectForm', () => {
<CreateUpdateProjectForm/>
</TrpcProvider>
)
let options = screen.getAllByRole('combobox')
console.log(options)
})

View File

@@ -12,12 +12,11 @@ import { SelectFormField, TextInputFormField } from '~/app/_components/Form/Fiel
import { FormScaffold } from '~/app/_components/Form/Components';
import { usePathname, useRouter } from 'next/navigation';
import { makeUseRelationShipWithNameIndex } from '~/lib/hooks';
import { FormMutationContextProvider } from '~/app/_components/Form/Components/MutationProvider';
export default function CreateUpdateProjectForm(params: { className?: string, entity?: IterableElement<RouterOutputs['project']['select']> }) {
const [id, setId] = useState<string|undefined>(params.entity ? params.entity.id : undefined)
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
const schemas = entitySchemas('project')
console.log('using trpc')
const { data:stacks,id:stackId, name:stackName,success:stacksSuccess, error:stackError } = makeUseRelationShipWithNameIndex('stackItems')(trpc.techStack.select.useQuery({}),id,(items) => {return items ? items.join('-') : ""})
console.log(stackError)
const { data: stacks, id: stackId, name: stackName, success: stacksSuccess, error: stackError } = makeUseRelationShipWithNameIndex('stackItems')(trpc.techStack.select.useQuery({}), id, (items) => { return items ? items.join('-') : "" })
const form = useForm<z.infer<typeof schemas.insert>>({
resolver: zodResolver(schemas.insert),
defaultValues: {
@@ -40,38 +39,42 @@ export default function CreateUpdateProjectForm(params: { className?: string, en
createMutation.mutate(values)
}
return (
<FormScaffold
form={form}
createMutation={createMutation}
updateMutation={updateMutation}
deleteMutation={deleteMutation}
onSubmit={onSubmit}
title='Project'
id={id}
className={params.className}
>
<SelectFormField control={form.control} name='stackId' label='Stack' defaultValue={stackId} placeholder={stackName} >
{
stacksSuccess ?
<>
{stacks?.map((stack) => {
return (<SelectItem key={stack.id} value={stack.id}> {stack.stackItems ? stack.stackItems.join("-") : "Empty Stack"} </SelectItem>)
})}
</> :
<SelectItem key='placeholder' value="placeholder" />
}
</SelectFormField>
<TextInputFormField control={form.control} name='title' label='Title' />
<SelectFormField control={form.control} name='sourceType' label='Source Type' defaultValue={'open'} placeholder='open' >
<SelectItem value="open"> open </SelectItem>
<SelectItem value="closed"> closed </SelectItem>
</SelectFormField>
<TextInputFormField control={form.control} label='Source Link' name='sourceLink' />
<SelectFormField control={form.control} name='releaseStatus' label='Release Status' defaultValue={'unreleased'} placeholder='unreleased' >
<SelectItem value="released"> released </SelectItem>
<SelectItem value="unreleased"> unreleased </SelectItem>
</SelectFormField>
<TextInputFormField control={form.control} label='Release Link' name='releaseLink' />
</FormScaffold>
<FormMutationContextProvider value={
{
createMutation: createMutation,
updateMutation: updateMutation,
deleteMutation: deleteMutation
}}>
<FormScaffold
form={form}
onSubmit={onSubmit}
title='Project'
id={id}
className={params.className}
>
<SelectFormField control={form.control} name='stackId' label='Stack' defaultValue={stackId} placeholder={stackName} >
{
stacksSuccess ?
<>
{stacks?.map((stack) => {
return (<SelectItem key={stack.id} value={stack.id}> {stack.stackItems ? stack.stackItems.join("-") : "Empty Stack"} </SelectItem>)
})}
</> :
<SelectItem key='placeholder' value="placeholder" />
}
</SelectFormField>
<TextInputFormField control={form.control} name='title' label='Title' />
<SelectFormField control={form.control} name='sourceType' label='Source Type' defaultValue={'open'} placeholder='open' >
<SelectItem value="open"> open </SelectItem>
<SelectItem value="closed"> closed </SelectItem>
</SelectFormField>
<TextInputFormField control={form.control} label='Source Link' name='sourceLink' />
<SelectFormField control={form.control} name='releaseStatus' label='Release Status' defaultValue={'unreleased'} placeholder='unreleased' >
<SelectItem value="released"> released </SelectItem>
<SelectItem value="unreleased"> unreleased </SelectItem>
</SelectFormField>
<TextInputFormField control={form.control} label='Release Link' name='releaseLink' />
</FormScaffold>
</FormMutationContextProvider>
)
}

View File

@@ -0,0 +1,55 @@
import { test, assert, expect } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import CreateUpdateStackForm from './CreateUpdateForm'
import TrpcProvider from '~/app/_trpc/TrpcProvider'
import { servTrpc } from '~/app/_trpc/ServerClient'
import { db } from '~/server/db'
import { sql } from 'drizzle-orm'
test('CreateUpdateProjectForm',async () => {
const user = userEvent.setup()
// console.log("db direct:" ,db.query.techStack.findMany());
render(
<TrpcProvider>
<CreateUpdateStackForm/>
</TrpcProvider>
)
const trigger = screen.getByTestId("multiboolean-popover-trigger")
fireEvent.click(trigger)
const content = screen.getAllByTestId("multiboolean-checkbox")
const checkfirst = content.at(0)
const checkSecond = content.at(1)
assert.notEqual(checkfirst,undefined)
assert.notEqual(checkSecond,undefined)
await user.click(checkfirst as HTMLElement)
await user.click(checkSecond as HTMLElement)
// TODO use findby: https://testing-library.com/docs/dom-testing-library/api-async because component has async effects and conditional rendering
// this shit just doesnt work
// https://github.com/jsdom/jsdom/issues/1937 why ? who knows
const submitButton = screen.getByTestId('form-submit')
screen.debug(submitButton)
// waitFor(() => {
// if ((submitButton as HTMLButtonElement).type != 'submit') {
// console.log("button isn submit")
// throw new Error('submit button props pending');
// }
// })
await user.click(submitButton)
screen.debug()
//TODO find out why the fuck this is timing out
await waitFor(() => {
screen.getByTestId('submitted')
},{timeout:3000})
let res
await waitFor(async () => {
return new Promise(async (resolve,reject) => {
res = await db.execute(sql`SELECT * from "gregorlohaus.com_tech_stack";`)
if (res.rows.length < 1) {
reject(false)
} else {
resolve(true)
}
})
},{timeout:3000})
console.log(res)
})

View File

@@ -2,19 +2,16 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { z } from "zod";
import { Form } from "~/components/ui/form";
import { trpc } from "~/app/_trpc/Client";
import { Button } from "~/components/ui/button";
import * as Card from '~/components/ui/card'
import type { IterableElement } from 'type-fest'
import { stackItemEnum } from "~/server/db/schema";
import { stackItemEnum } from "~/server/dbschema/schema";
import type { RouterOutputs } from '~/server/routers/_app';
import { entitySchemas, makeOnSuccess } from '~/lib/utils';
import { useState } from 'react';
import { MultiBooleanFormField } from "~/app/_components/Form/Fields"
import { DependentFormMessaage, FormScaffold } from '~/app/_components/Form/Components';
import DependentText from '~/app/_components/DependentText';
import { FormScaffold } from '~/app/_components/Form/Components';
import { usePathname, useRouter } from 'next/navigation';
import { FormMutationContextProvider } from '~/app/_components/Form/Components/MutationProvider';
export default function CreateUpdateStackForm(params: { className?: string, entity?: IterableElement<RouterOutputs['techStack']['select']> }) {
const schemas = entitySchemas('techStack')
@@ -25,6 +22,7 @@ export default function CreateUpdateStackForm(params: { className?: string, enti
stackItems: params.entity ? params.entity.stackItems : [],
}
})
const [submitted, setSubmitted] = useState(false)
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
let path = usePathname()
let router = useRouter()
@@ -32,22 +30,25 @@ export default function CreateUpdateStackForm(params: { className?: string, enti
const updateMutation = trpc.techStack.update.useMutation({ onSuccess: makeOnSuccess('update', form) })
const deleteMutation = trpc.techStack.delete.useMutation({ onSuccess: makeOnSuccess('delete', form, undefined, path, router) })
function onSubmit(values: z.infer<typeof schemas.insert>) {
setSubmitted(true)
params.entity ?
updateMutation.mutate(values) :
createMutation.mutate(values)
createMutation.mutate(values);
}
return (
<FormScaffold
form={form}
createMutation={createMutation}
updateMutation={updateMutation}
deleteMutation={deleteMutation}
onSubmit={onSubmit}
title='Entry'
id={id}
className={params.className}
>
<MultiBooleanFormField control={form.control} name='stackItems' label='Stack Items' options={stackItemEnum.enumValues} defaultValues={params.entity?.stackItems} />
</FormScaffold>
<>
<div data-testid={submitted ? "submitted" : ""}/>
<FormMutationContextProvider value={{ createMutation: createMutation, updateMutation: updateMutation, deleteMutation: deleteMutation }}>
<FormScaffold
form={form}
onSubmit={onSubmit}
title='Entry'
id={id}
className={params.className}
>
<MultiBooleanFormField control={form.control} name='stackItems' label='Stack Items' options={stackItemEnum.enumValues} defaultValues={params.entity?.stackItems ?? [""]} />
</FormScaffold>
</FormMutationContextProvider>
</>
)
}

View File

@@ -40,16 +40,20 @@ function Button({
variant,
size,
asChild = false,
submit = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
submit?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
type={submit ? "submit" : props.type}
data-testid={submit ? "form-submit" : undefined}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>

View File

@@ -1,7 +1,7 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
import { createSchemaFactory, createSelectSchema, type BuildSchema } from 'drizzle-zod'
import * as schema from "~/server/db/schema";
import * as schema from '~/server/dbschema/schema'
import { PgTable } from "drizzle-orm/pg-core";
import type { FieldValues, Path, UseFormReturn } from "react-hook-form";
import type { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";

View File

@@ -2,7 +2,7 @@
// import postgres from "postgres";
import { env } from "~/env";
import * as schema from "./schema";
import * as schema from "~/server/dbschema/schema";
// /**
// * Cache the database connection in development. This avoids creating a new connection on every HMR

View File

@@ -1,7 +1,7 @@
import type { Entries } from "type-fest";
import { publicProcedure, router } from "~/server/trpc";
import { db } from "~/server/db";
import * as schema from "~/server/db/schema";
import * as schema from '~/server/dbschema/schema';
import { isAdmin } from "~/app/actions";
import { TRPCError } from "@trpc/server";
import { entitySchemas, type Schema, type SchemaKeys } from "~/lib/utils";
@@ -20,6 +20,7 @@ export function trpcCrudRouterFromDrizzleEntity<T extends SchemaKeys<Schema>>(ta
}
let r = router({
select: publicProcedure.input(schemas.update).query(async (opts) => {
console.log("select with:", opts.input)
const { input } = opts;
if (input === undefined) {
return await db.select().from(schema[table]).execute();
@@ -36,6 +37,7 @@ export function trpcCrudRouterFromDrizzleEntity<T extends SchemaKeys<Schema>>(ta
return await db.select().from(schema[table]).execute();
}),
update: publicProcedure.input(schemas.update).mutation(async (opts) => {
console.log("update with:",opts.input)
let admin = await isAdmin();
if (!admin) {
throw new TRPCError(
@@ -52,6 +54,7 @@ export function trpcCrudRouterFromDrizzleEntity<T extends SchemaKeys<Schema>>(ta
return await db.update(schema[table]).set(input).where(eq(schema[table]['id'], input['id'])).returning().execute();
}),
insert: publicProcedure.input(schemas.insert).mutation(async (opts) => {
console.log("insert with:",opts.input)
let admin = await isAdmin();
if (!admin) {
throw new TRPCError(
@@ -62,6 +65,7 @@ export function trpcCrudRouterFromDrizzleEntity<T extends SchemaKeys<Schema>>(ta
return await db.insert(schema[table]).values(input).returning().execute();
}),
delete: publicProcedure.input(schemas.update).mutation(async (opts) => {
console.log("delete with:",opts.input)
let admin = await isAdmin();
if (!admin) {
throw new TRPCError(