testing setup
This commit is contained in:
@@ -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()) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
29
src/app/_components/Form/Components/CollapsibleForm.tsx
Normal file
29
src/app/_components/Form/Components/CollapsibleForm.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
18
src/app/_components/Form/Components/DeleteButton.tsx
Normal file
18
src/app/_components/Form/Components/DeleteButton.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
47
src/app/_components/Form/Components/FormScaffold.tsx
Normal file
47
src/app/_components/Form/Components/FormScaffold.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
10
src/app/_components/Form/Components/SubmitButton.tsx
Normal file
10
src/app/_components/Form/Components/SubmitButton.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
5
src/app/_components/Form/Components/index.ts
Normal file
5
src/app/_components/Form/Components/index.ts
Normal 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'
|
||||
23
src/app/_components/Form/Fields/BooleanFormField.tsx
Normal file
23
src/app/_components/Form/Fields/BooleanFormField.tsx
Normal 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>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
54
src/app/_components/Form/Fields/CalenderFormField.tsx
Normal file
54
src/app/_components/Form/Fields/CalenderFormField.tsx
Normal 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>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
25
src/app/_components/Form/Fields/MdeFormField.tsx
Normal file
25
src/app/_components/Form/Fields/MdeFormField.tsx
Normal 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>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
92
src/app/_components/Form/Fields/MultiBooleanFormField.tsx
Normal file
92
src/app/_components/Form/Fields/MultiBooleanFormField.tsx
Normal 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>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
)}
|
||||
/>
|
||||
6
src/app/_components/Form/Fields/index.ts
Normal file
6
src/app/_components/Form/Fields/index.ts
Normal 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'
|
||||
@@ -9,7 +9,7 @@ export default function MdeFormField<T extends FieldValues>(params: { control: C
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Description
|
||||
{params.label}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<MDEditor
|
||||
|
||||
Reference in New Issue
Block a user