testing setup

This commit is contained in:
2025-08-29 14:19:38 +02:00
parent e74b30e04c
commit 869cc07fdd
46 changed files with 6710 additions and 1460 deletions

View File

@@ -1,4 +1,4 @@
import { isAdmin } from "../actions"
import { isAdmin } from "~/app/actions"
export default async function AdminWrap({children,}: Readonly<{ children: React.ReactNode }>) {
if (await isAdmin()) {

View File

@@ -42,7 +42,6 @@ export default function CalendarFormField<T extends FieldValues>(params: { contr
disabled={(date) =>
date > new Date() || date < new Date("1900-01-01")
}
initialFocus
captionLayout="dropdown"
/>
</PopoverContent>

View File

@@ -0,0 +1,29 @@
import { ChevronsUpDown, Plus } from "lucide-react";
import { Button } from "~/components/ui/button";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
type FormProps<T> = {
className?:string,
entity?:T
}
export default function CollapsibleForm<T>(params: {entityName:string, entity?:T, entityLabelIndex?:keyof T,form:React.ComponentType<FormProps<T>>}) {
return (
<Collapsible>
<CollapsibleTrigger asChild>
<Button variant={"ghost"}>
{ params.entity ?
<>{params.entityLabelIndex ? params.entity[params.entityLabelIndex] : params.entityName} <ChevronsUpDown/></> : <> New {params.entityName} <Plus/></>
}
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="autoAlpha">
{
params.entity ?
<params.form className="w-full" entity={params.entity}/> :
<params.form className="w-full"/>
}
</CollapsibleContent>
</Collapsible>
)
}

View File

@@ -0,0 +1,18 @@
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";
export default function DeleteButton<TD, TE, TV, TC>(params: { mutation: UseTRPCMutationResult<TD, TE, TV, TC>, id?: string }) {
if (params.id) {
return (
<Button variant='destructive' onClick={() => params.mutation.mutate(({ id: params.id } as TV))}>
<DeleteIcon />
</Button>
)
}
return (
<Button variant='destructive' disabled>
<DeleteIcon />
</Button>
)
}

View File

@@ -0,0 +1,47 @@
import * as Card from '~/components/ui/card'
import { Form } from "~/components/ui/form";
import { DependentFormMessaage, SubmitButton, DeleteButton } from '~/app/_components/Form/Components';
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: {
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,
id?: string,
className?: string
}) {
const { form, onSubmit, createMutation, deleteMutation, updateMutation, title, id, className, children } = params
return (
<Card.Card className={className ? className : "w-5/6 lg:w-1/2"}>
<Card.CardHeader>
<Card.CardTitle>
<DependentText bool={id ? true : false} true={`Update ${title}`} false={`Create ${title}`} />
</Card.CardTitle>
</Card.CardHeader>
<Card.CardContent>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8"
>
{children}
<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} />
</div>
</form>
</Form>
</Card.CardContent>
</Card.Card>
)
}

View File

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

View File

@@ -0,0 +1,5 @@
export { default as DependentFormMessaage } from './MutationFormMessage'
export { default as SubmitButton } from './SubmitButton'
export { default as DeleteButton } from './DeleteButton'
export { default as FormScaffold } from './FormScaffold'
export { default as CollapsibleForm } from './CollapsibleForm'

View File

@@ -0,0 +1,23 @@
import type { Control, FieldValues, Path } from "react-hook-form";
import { Checkbox } from "~/components/ui/checkbox";
import { FormControl, FormField, FormItem, FormLabel } from "~/components/ui/form";
export default function BooleanFormField<T extends FieldValues>(params: { control: Control<T>, name: Path<T>, label: string }) {
return (
<FormField
control={params.control}
name={params.name}
render={({ field }) => (
<FormItem>
<FormLabel>{params.label}</FormLabel>
<FormControl>
<Checkbox
checked={field.value ? field.value : false}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
)
}

View File

@@ -0,0 +1,54 @@
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import type { Control, FieldValues, Path } from "react-hook-form";
import { Button } from "~/components/ui/button";
import { Calendar } from "~/components/ui/calendar";
import { FormControl, FormField, FormItem, FormLabel } from "~/components/ui/form";
import { Input } from "~/components/ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";
import { cn } from "~/lib/utils";
export default function CalendarFormField<T extends FieldValues>(params: { control: Control<T>, name: Path<T>, label: string }) {
return (
<FormField
control={params.control}
name={params.name}
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>{params.label}</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant={"outline"}
className={cn(
"w-[240px] pl-3 text-left font-normal",
!field.value && "text-muted-foreground"
)}
>
{field.value ? (
format(field.value, "PPP")
) : (
<span>Pick a date</span>
)}
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={field.value}
onSelect={field.onChange}
disabled={(date) =>
date > new Date() || date < new Date("1900-01-01")
}
initialFocus
captionLayout="dropdown"
/>
</PopoverContent>
</Popover>
</FormItem>
)}
/>
)
}

View File

@@ -0,0 +1,25 @@
import MDEditor from "@uiw/react-md-editor";
import type { Control, FieldValues, Path } from "react-hook-form";
import { FormControl, FormField, FormItem, FormLabel } from "~/components/ui/form";
export default function MdeFormField<T extends FieldValues>(params: { control: Control<T>, name: Path<T>, label: string, dataColorMode: "dark"|"light" }) {
return (
<FormField
control={params.control}
name={params.name}
render={({ field }) => (
<FormItem>
<FormLabel>
Description
</FormLabel>
<FormControl>
<MDEditor
value={field.value ? field.value : ""}
onChange={field.onChange}
data-color-mode={params.dataColorMode}
/>
</FormControl>
</FormItem>
)}
/>
)
}

View File

@@ -0,0 +1,92 @@
import type { CheckedState } from "@radix-ui/react-checkbox";
import { ChevronDownIcon } from "lucide-react";
import { createContext,useContext, useState } from "react";
import { useFormContext, type Control, type ControllerRenderProps, type FieldValues, type Path } from "react-hook-form";
import type { Entries } from "type-fest";
import { Button } from "~/components/ui/button";
import { Checkbox } from "~/components/ui/checkbox";
import { FormControl, FormField, FormItem, FormLabel } from "~/components/ui/form";
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";
interface MultiBooleanFieldContextProps {
checkedValues:Record<string,boolean>;
setCheckedValue:(arg0:Record<string,boolean>) => void;
}
const MultiBooleanFieldContext = createContext<MultiBooleanFieldContextProps|undefined>(undefined)
function InnerMultiBooleanFormField(params: { options: string[], onChange: (arg0: string[]) => void }) {
const context = useContext(MultiBooleanFieldContext)
if (context === undefined) {
return (<></>)
}
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
}
context.setCheckedValue(state)
let stateArr: string[] = []
for (key in state) {
if (state[key]) {
stateArr.push(key)
}
}
console.log('calling field on change with:', stateArr)
params.onChange(stateArr)
}
}
const checked = (key: string) => {
if (context.checkedValues[key] === undefined) {
return undefined
}
return context.checkedValues[key]
}
return (
<>
{
params.options.map((opt) => (
<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)} />
</div>
</FormItem>
))
}
</>
)
}
export default function MultiBooleanFormField<T extends FieldValues>(params: { control: Control<T>, name: Path<T>, label: string, options: string[], defaultValues?: string[] }) {
const [open,setOpen] = useState<boolean>(false)
const [checkedValues, setCheckedValues] = useState<Record<string, boolean>>(params.defaultValues == undefined ? {} : params.defaultValues.reduce((acc,current,index) => { acc[current] = true; return acc },{}))
return (
<FormField
control={params.control}
name={params.name}
render={({ field }) => (
<FormItem>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant={'ghost'}>
<FormLabel>{params.label}</FormLabel>
<ChevronDownIcon/>
</Button>
</PopoverTrigger>
<FormControl>
<PopoverContent>
<MultiBooleanFieldContext.Provider value={{checkedValues: checkedValues, setCheckedValue: setCheckedValues}}>
<InnerMultiBooleanFormField onChange={field.onChange} options={params.options} />
</MultiBooleanFieldContext.Provider>
</PopoverContent>
</FormControl>
</Popover>
</FormItem>
)}
/>
)
}

View File

@@ -2,7 +2,7 @@ import type { ReactNode } from "react";
import type { Control, FieldValues, Path } from "react-hook-form";
import { FormField,FormControl, FormItem, FormLabel } from "~/components/ui/form";
import { Select, SelectContent, SelectTrigger, SelectValue } from "~/components/ui/select";
export default function SelectFormField<T extends FieldValues>(params: { control: Control<T>, name: Path<T>, label: string, defaultValue:string|undefined, placeholder:string|undefined, children: ReactNode}) {
export default function SelectFormField<T extends FieldValues>(params: { control: Control<T>, name: Path<T>, label: string, defaultValue?:string|null, placeholder?:string|null, children: ReactNode}) {
return (
<FormField
control={params.control}
@@ -12,10 +12,10 @@ export default function SelectFormField<T extends FieldValues>(params: { control
<FormLabel>
{params.label}
</FormLabel>
<Select onValueChange={field.onChange} value={field.value == null ? undefined : field.value} defaultValue={params.defaultValue}>
<Select onValueChange={field.onChange} value={field.value == null ? undefined : field.value} defaultValue={params.defaultValue ? params.defaultValue : field.value == null ? undefined : field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={params.placeholder} />
<SelectValue placeholder={params.placeholder ? undefined : params.placeholder} />
</SelectTrigger>
</FormControl>
<SelectContent>

View File

@@ -9,7 +9,7 @@ export default function TexttInputFormField<T extends FieldValues>(params: { con
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>{params.label}</FormLabel>
<Input placeholder="release link" onChange={field.onChange} value={field.value == null ? undefined : field.value} />
<Input placeholder={params.name} onChange={field.onChange} value={field.value == null ? undefined : field.value} />
</FormItem>
)}
/>

View File

@@ -0,0 +1,6 @@
export { default as BooleanFormField } from './BooleanFormField'
export { default as TextInputFormField } from './TextInputFormField'
export { default as MultiBooleanFormField } from './MultiBooleanFormField'
export { default as SelectFormField } from './SelectFormField'
export { default as MdeFormField } from './MdeFormField'
export { default as CalenderFormField } from './CalenderFormField'

View File

@@ -9,7 +9,7 @@ export default function MdeFormField<T extends FieldValues>(params: { control: C
render={({ field }) => (
<FormItem>
<FormLabel>
Description
{params.label}
</FormLabel>
<FormControl>
<MDEditor