diff --git a/src/app/_components/CalenderFormField.tsx b/src/app/_components/CalenderFormField.tsx new file mode 100644 index 0000000..3ad9e12 --- /dev/null +++ b/src/app/_components/CalenderFormField.tsx @@ -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(params: { control: Control, name: Path, label: string }) { + return ( + ( + + {params.label} + + + + + + + + + date > new Date() || date < new Date("1900-01-01") + } + initialFocus + captionLayout="dropdown" + /> + + + + )} + /> + ) +} diff --git a/src/app/_components/DependentText.tsx b/src/app/_components/DependentText.tsx new file mode 100644 index 0000000..020a081 --- /dev/null +++ b/src/app/_components/DependentText.tsx @@ -0,0 +1,10 @@ +export default function DependentText(params: {true:string,false:string,bool:boolean}) { + return ( + <> + { params.bool ? + params.true : + params.false + } + + ) +} diff --git a/src/app/_components/MdeFormField.tsx b/src/app/_components/MdeFormField.tsx new file mode 100644 index 0000000..f4baf15 --- /dev/null +++ b/src/app/_components/MdeFormField.tsx @@ -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(params: { control: Control, name: Path, label: string, dataColorMode: "dark"|"light" }) { + return ( + ( + + + Description + + + + + + )} + /> + ) +} diff --git a/src/app/_components/MutationFormMessage.tsx b/src/app/_components/MutationFormMessage.tsx new file mode 100644 index 0000000..bb2d7b6 --- /dev/null +++ b/src/app/_components/MutationFormMessage.tsx @@ -0,0 +1,21 @@ +import { FormMessage } from "~/components/ui/form"; + +interface Error { + message: string +} + +export default function DependentFormMessaage( params: {trueStatus: string, falseStatus: string, falseError?: Error|null, trueError?:Error|null, bool: boolean} ) { + return ( + <> + { + params.bool ? + + {params.trueError ? params.trueError.message : params.trueStatus} + : + + {params.falseError ? params.falseError.message : params.falseStatus} + + } + + ) +} diff --git a/src/app/_components/SelectFormField.tsx b/src/app/_components/SelectFormField.tsx new file mode 100644 index 0000000..4031f2c --- /dev/null +++ b/src/app/_components/SelectFormField.tsx @@ -0,0 +1,29 @@ +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(params: { control: Control, name: Path, label: string, defaultValue:string|undefined, placeholder:string|undefined, children: ReactNode}) { + return ( + ( + + + {params.label} + + + + )} + /> + ) +} diff --git a/src/app/_components/TextInputFormField.tsx b/src/app/_components/TextInputFormField.tsx new file mode 100644 index 0000000..46e5f87 --- /dev/null +++ b/src/app/_components/TextInputFormField.tsx @@ -0,0 +1,17 @@ +import type { Control, FieldValues, Path } from "react-hook-form"; +import { FormField, FormItem, FormLabel } from "~/components/ui/form"; +import { Input } from "~/components/ui/input"; +export default function TexttInputFormField(params: { control: Control, name: Path, label:string }) { + return ( + ( + + {params.label} + + + )} + /> + ) +} diff --git a/src/app/admin/cv/category/[id]/page.tsx b/src/app/admin/cv/category/[id]/page.tsx index cd07792..fa00375 100644 --- a/src/app/admin/cv/category/[id]/page.tsx +++ b/src/app/admin/cv/category/[id]/page.tsx @@ -1,12 +1,17 @@ -'use server' +'use client' -import UpdateCvCategoryForm from "../_components/UpdateForm"; +import { trpc } from "~/app/_trpc/Client"; +import { useParams } from "next/navigation"; +import CreateUpdateCvCategoryForm from "../_components/CreateUpdateForm"; -export default async function Page({params}:{params: Promise<{id:string}>}) { - console.log(params) - const {id} = await params; - return ( - - ) +export default function Page() { + const {id} = useParams<{id: string}>(); + const {data} = trpc.category.select.useQuery({id: id}) + if (data !== undefined && data.length > 0) { + return ( + + ) + } + return (<>) } diff --git a/src/app/admin/cv/category/_components/CollapsibleForm.tsx b/src/app/admin/cv/category/_components/CollapsibleForm.tsx index 4cc1874..54dfa95 100644 --- a/src/app/admin/cv/category/_components/CollapsibleForm.tsx +++ b/src/app/admin/cv/category/_components/CollapsibleForm.tsx @@ -1,12 +1,10 @@ import { ChevronsUpDown, Plus } from "lucide-react" import { Button } from "~/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible"; -import { type EntryRouterOutputs } from "~/server/routers/cv/entry"; -import type { CategoryRouterOutputs } from "~/server/routers/cv/category"; import { type Element } from "~/lib/utils"; -import UpdateCvCategoryForm from "./UpdateForm"; -import CreateCvCategoryForm from "./CreateForm"; -export default function CollapsibleCvCategoryForm(params:{category?:Element}) { +import CreateUpdateCvCategoryForm from "./CreateUpdateForm"; +import type { RouterOutputs } from "~/server/routers/_app"; +export default function CollapsibleCvCategoryForm(params:{category?:Element}) { return ( @@ -19,8 +17,8 @@ export default function CollapsibleCvCategoryForm(params:{category?:Element { params.category ? - : - + : + } diff --git a/src/app/admin/cv/category/_components/CreateForm.tsx b/src/app/admin/cv/category/_components/CreateUpdateForm.tsx similarity index 59% rename from src/app/admin/cv/category/_components/CreateForm.tsx rename to src/app/admin/cv/category/_components/CreateUpdateForm.tsx index ef80f6a..04d6a21 100644 --- a/src/app/admin/cv/category/_components/CreateForm.tsx +++ b/src/app/admin/cv/category/_components/CreateUpdateForm.tsx @@ -1,48 +1,43 @@ 'use client' -import { insertSchema } from "~/lib/schema/cv/category" import { zodResolver } from '@hookform/resolvers/zod' import { useForm } from 'react-hook-form' import { z } from "zod"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form"; +import { Form, FormControl, FormField, FormItem, FormLabel } from "~/components/ui/form"; import { Input } from "~/components/ui/input"; import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~/components/ui/select"; 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 { usePathname, useRouter } from "next/navigation"; -import { entitySchemas } from "~/lib/utils"; +import { entitySchemas, makeOnSuccess } from "~/lib/utils"; import type { RouterOutputs } from "~/server/routers/_app"; -export default function CreateCvCategoryForm(params:{className?:string,category?:IterableElement}) { +import DependentFormMessaage from '~/app/_components/MutationFormMessage'; +import { useState } from 'react'; +import DependentText from '~/app/_components/DependentText'; +export default function CreateUpdateCvCategoryForm(params:{className?:string,category?:IterableElement,isUpdate?:boolean}) { const schemas = entitySchemas('cvCategory') - const pathname = usePathname(); - const router = useRouter(); + const [isUpdate,setIsUpdate] = useState(params.isUpdate ? params.isUpdate : (params.category ? true : false)) const form = useForm>({ resolver: zodResolver(schemas.insert), defaultValues: { - id: crypto.randomUUID(), - layoutPosition: "col1" + id: params.category ? params.category.id : crypto.randomUUID(), + name: params.category ? params.category.name : "", + layoutPosition: params.category ? params.category.layoutPosition : "col1" } }) - const createMutation = trpc.cv.categoryv2.insert.useMutation() + const createMutation = trpc.category.insert.useMutation({onSuccess: makeOnSuccess('create',form,setIsUpdate)}) + const updateMutation = trpc.category.update.useMutation({onSuccess: makeOnSuccess('update',form)}) function onSubmit(values: z.infer) { - const res = createMutation.mutate(values) + isUpdate ? + updateMutation.mutate(values) : + createMutation.mutate(values) } - const updateMutation = trpc.cv.categoryv2.update.useMutation() - const deleteMutation = trpc.cv.categoryv2.delete.useMutation({ - onSuccess: () => { - if (pathname.includes('list')) { - router.refresh() - } else { - router.back() - } - } - }) + //TODO use SelectFormField and TextInputFormField return ( - Create Category + @@ -60,7 +55,7 @@ export default function CreateCvCategoryForm(params:{className?:string,category? Name - + )} @@ -80,7 +75,7 @@ export default function CreateCvCategoryForm(params:{className?:string,category? - {insertSchema.shape.layoutPosition.unwrap().unwrap().options.map((o) => ( + {schemas.insert.shape.layoutPosition.unwrap().unwrap().options.map((o) => ( {o} ))} @@ -90,10 +85,10 @@ export default function CreateCvCategoryForm(params:{className?:string,category? )} > - - - {createMutation.error ? createMutation.error.message : createMutation.status} - + + diff --git a/src/app/admin/cv/category/_components/UpdateForm.tsx b/src/app/admin/cv/category/_components/UpdateForm.tsx deleted file mode 100644 index 7825661..0000000 --- a/src/app/admin/cv/category/_components/UpdateForm.tsx +++ /dev/null @@ -1,146 +0,0 @@ -'use client' -import { updateRouteSchema } from "~/lib/schema/cv/category" -import { zodResolver } from '@hookform/resolvers/zod' -import { useForm } from 'react-hook-form' -import { z } from "zod"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form"; -import { Input } from "~/components/ui/input"; -import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~/components/ui/select"; -import { trpc } from "~/app/_trpc/Client"; -import { Button } from "~/components/ui/button"; -import * as Card from '~/components/ui/card' -import { useRouter, usePathname } from "next/navigation"; -import { Delete } from "lucide-react"; -import { cn } from "~/lib/utils"; -export default function UpdateCvCategoryForm(params: { id: string, className?: string }) { - const router = useRouter(); - const pathname = usePathname(); - const id = params.id; - const category = trpc.cv.category.get.useQuery({ id: id }) - const form = useForm>({ - resolver: zodResolver(updateRouteSchema), - defaultValues: { - by: { id: id }, - update: { - layoutPosition: category.data?.layoutPosition, - name: category.data?.layoutPosition - } - } - }) - category.promise.then((data) => { - form.setValue("update.layoutPosition", data?.layoutPosition) - form.setValue("update.name", data?.name) - }) - const updateMutation = trpc.cv.category.update.useMutation({ - onSuccess: () => { - category.refetch() - } - }) - const deleteMutation = trpc.cv.category.delete.useMutation({ - onSuccess: () => { - if (pathname.includes('list')) { - router.refresh() - } else { - router.back() - } - } - }) - const deleteCategory = (id: string) => { - console.log(`deleting ${id}`) - deleteMutation.mutate(id) - } - function onSubmit(values: z.infer) { - updateMutation.mutate({ - by: { id: id }, - update: { - layoutPosition: values.update.layoutPosition, - name: values.update.name - } - }) - } - if (category.data !== undefined) { - return ( - - - - Update Category - - - -
- - ( - - - Name - - - - - - )} - > - - ( - - - - - - )} - > - - ( - - - Layout Position - - - - - - )} - > - - - - {updateMutation.error ? updateMutation.error.message : updateMutation.status} - - -
- -
-
- ) - } else { - return ( - <> - - ) - } -} diff --git a/src/app/admin/cv/category/create/page.tsx b/src/app/admin/cv/category/create/page.tsx index 2445baf..1e41eb8 100644 --- a/src/app/admin/cv/category/create/page.tsx +++ b/src/app/admin/cv/category/create/page.tsx @@ -1,8 +1,8 @@ 'use client' -import CreateCvCategoryForm from "../_components/CreateForm"; +import CreateUpdateCvCategoryForm from "../_components/CreateUpdateForm"; export default function Page() { return ( - + ) } diff --git a/src/app/admin/cv/category/list/page.tsx b/src/app/admin/cv/category/list/page.tsx index 0e085ea..81d3da5 100644 --- a/src/app/admin/cv/category/list/page.tsx +++ b/src/app/admin/cv/category/list/page.tsx @@ -2,26 +2,27 @@ import Link from "next/link"; import { trpc } from "~/app/_trpc/Client"; import { useGSAP } from '@gsap/react' -import { useRef } from "react"; +import { Suspense, useRef } from "react"; import * as Card from '~/components/ui/card' import { useGsapContext } from "~/app/_providers/GsapProvicer"; import CollapsibleCvEntryForm from "../../entry/_components/CollapsibleForm"; -import UpdateCvCategoryForm from "../_components/UpdateForm"; import CollapsibleCvCategoryForm from "../_components/CollapsibleForm"; +import CreateUpdateCvCategoryForm from "../_components/CreateUpdateForm"; export default function CvPage() { - const cvCategories = trpc.cv.category.list.useQuery(undefined,{refetchInterval:1000}); + const categories = trpc.category.select.useQuery({},{refetchInterval:1000}); + const entires = trpc.entry.select.useSuspenseQuery({},{refetchInterval:1000}) const gsap = useGsapContext() const container = useRef(null); useGSAP(() => { gsap?.from('.gsapan', { x: -100, opacity: 0, duration: 0.5, stagger: { each: 0.3 } }); - }, { scope: container, dependencies: [cvCategories.status], revertOnUpdate: true }); + }, { scope: container, dependencies: [categories.status], revertOnUpdate: true }); return (
- {cvCategories.data == undefined ? + {categories.data == undefined ?
: <> - {cvCategories.data.map((cat) => { + {categories.data.map((cat) => { return ( @@ -33,20 +34,22 @@ export default function CvPage() {
- +
Entries: + )}>
{ - cat.cvEntry.length > 0 ? ( + entires[0].filter((e) => {return e.categoryId == cat.id}).length > 0 ? ( <> - {cat.cvEntry.map((entry) => ( + {entires[0].filter((e) => {return e.categoryId == cat.id}).map((entry) => ( ))} ) : (<>) }
+
diff --git a/src/app/admin/cv/entry/[id]/page.tsx b/src/app/admin/cv/entry/[id]/page.tsx index 959b455..0495dbd 100644 --- a/src/app/admin/cv/entry/[id]/page.tsx +++ b/src/app/admin/cv/entry/[id]/page.tsx @@ -1,12 +1,16 @@ -'use server' +'use client' -import UpdateCvEntryForm from "../_components/UpdateForm"; +import { trpc } from "~/app/_trpc/Client"; +import { useParams } from "next/navigation"; +import CreateUpdateCvEntryForm from "../_components/CreateUpdateForm"; - -export default async function Page({params}:{params: Promise<{id:string}>}) { - console.log(params) - const {id} = await params; - return ( - - ) +export default function Page() { + const {id} = useParams<{id: string}>(); + const {data} = trpc.entry.select.useQuery({id: id}) + if (data !== undefined && data.length > 0) { + return ( + + ) + } + return (<>) } diff --git a/src/app/admin/cv/entry/_components/CollapsibleForm.tsx b/src/app/admin/cv/entry/_components/CollapsibleForm.tsx index 74ad2b4..039a280 100644 --- a/src/app/admin/cv/entry/_components/CollapsibleForm.tsx +++ b/src/app/admin/cv/entry/_components/CollapsibleForm.tsx @@ -1,12 +1,10 @@ import { ChevronsUpDown, Plus } from "lucide-react" import { Button } from "~/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible"; -import { type EntryRouterOutputs } from "~/server/routers/cv/entry"; -import type { CategoryRouterOutputs } from "~/server/routers/cv/category"; -import { type Element } from "~/lib/utils"; -import UpdateCvEntryForm from "./UpdateForm"; -import CreateCvEntryForm from "./CreateForm"; -export default function CollapsibleCvEntryForm(params:{entry?:Element['cvEntry']>|EntryRouterOutputs['get']|Element, categoryId?: string}) { +import CreateUpdateCvEntryForm from "./CreateUpdateForm"; +import type { IterableElement } from "type-fest"; +import type { RouterOutputs } from "~/server/routers/_app"; +export default function CollapsibleCvEntryForm(params:{entry?:IterableElement, categoryId?: string}) { return ( @@ -19,9 +17,8 @@ export default function CollapsibleCvEntryForm(params:{entry?:Element { params.entry ? - : - - + : + } diff --git a/src/app/admin/cv/entry/_components/CreateForm.tsx b/src/app/admin/cv/entry/_components/CreateForm.tsx deleted file mode 100644 index 468c6eb..0000000 --- a/src/app/admin/cv/entry/_components/CreateForm.tsx +++ /dev/null @@ -1,254 +0,0 @@ -'use client' -import { insertSchema, insertSchemaForm } from "~/lib/schema/cv/entry" -import { zodResolver } from '@hookform/resolvers/zod' -import { useForm } from 'react-hook-form' -import { format } from 'date-fns' -import { z } from "zod"; -import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form"; -import { Input } from "~/components/ui/input"; -import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~/components/ui/select"; -import { trpc } from "~/app/_trpc/Client"; -import { Button } from "~/components/ui/button"; -import * as Card from '~/components/ui/card' -import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover"; -import { CalendarIcon } from "lucide-react"; -import { Calendar } from "~/components/ui/calendar"; -import { cn } from "~/lib/utils"; -import { useEffect, useState } from "react" -import { Checkbox } from "~/components/ui/checkbox" -import MDEditor from '@uiw/react-md-editor' -import { useTheme } from "next-themes" - -export default function CreateCvEntryForm(params:{className:string|undefined, categoryId:string|undefined}) { - const { theme } = useTheme() - const categories = trpc.cv.category.list.useQuery() - const [categorySelectPlaceHolder,setCategorySelectPlaceHolder] = useState("Select category") - const [categorySelectDefaultValue,setCategorySelectDefaultValue] = useState("") - const [mdeTab,setMdeTab] = useState<"write" | "preview" | undefined>("write"); - useEffect(() => { - console.log('category success effect') - if (categories.data !== undefined) { - setCategorySelectDefaultValue(categories.data[0]?.id? categories.data[0].id : "") - setCategorySelectPlaceHolder(categories.data[0]?.name? categories.data[0].name : "Select category") - } - if (params.categoryId) { - let matching = categories.data?.filter((c) => { - return c.id == params.categoryId - }) - if (matching !== undefined && matching.length > 0) { - setCategorySelectDefaultValue(matching[0]?.id ? matching[0].id : categorySelectDefaultValue ) - setCategorySelectPlaceHolder(matching[0]?.name ? matching[0].name : categorySelectPlaceHolder) - } - } - },[categories.isSuccess]) - const now = new Date(); - now.setTime(Date.now()); - const form = useForm>({ - resolver: zodResolver(insertSchemaForm), - defaultValues: { - id: crypto.randomUUID(), - title: "", - description: "", - categoryId: params.categoryId ? params.categoryId : "", - fromTime: now, - toTime: now, - hideDates: false, - - } - }) - const mutation = trpc.cv.entry.create.useMutation({ - onSuccess: (data) => { form.setValue("id", data[0] ? data[0].id : "") } - }) - function onSubmit(values: z.infer) { - let { id, title, categoryId, description, hideDates } = values - let v: z.infer = { - id: id, - categoryId: categoryId, - description: description, - title: title, - fromTime: format(values.fromTime,'yyyy-MM-dd'), - toTime: format(values.toTime,'yyyy-MM-dd'), - hideDates: hideDates - } - mutation.mutate(v) - form.setValue("id", crypto.randomUUID()) - } - return ( - - - - Create Entry - - - -
- - ( - - - Category - - { - categories.data ? ( - - ) : - } - - )} - /> - ( - - - Title - - - - - - )} - /> - ( - - - Description - - - - - - )} - /> - ( - - From Date - - - - - - - - - date > new Date() || date < new Date("1900-01-01") - } - initialFocus - /> - - - - - )} - /> - ( - - To Date - - - - - - - - - date > new Date() || date < new Date("1900-01-01") - } - initialFocus - /> - - - - - )} - /> - ( - - Hide dates - - - - - )} - /> - - - {mutation.error ? mutation.error.message : mutation.status} - - - -
-
- ) -} diff --git a/src/app/admin/cv/entry/_components/CreateUpdateForm.tsx b/src/app/admin/cv/entry/_components/CreateUpdateForm.tsx new file mode 100644 index 0000000..58585b8 --- /dev/null +++ b/src/app/admin/cv/entry/_components/CreateUpdateForm.tsx @@ -0,0 +1,119 @@ +'use client' +import { zodResolver } from '@hookform/resolvers/zod' +import { useForm, type UseFormReturn } from 'react-hook-form' +import { format } from 'date-fns' +import { z } from "zod"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form"; +import { Input } from "~/components/ui/input"; +import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~/components/ui/select"; +import { trpc } from "~/app/_trpc/Client"; +import { Button } from "~/components/ui/button"; +import * as Card from '~/components/ui/card' +import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover"; +import { CalendarIcon } from "lucide-react"; +import { Calendar } from "~/components/ui/calendar"; +import { cn, entitySchemas, ft, makeOnSuccess, tt, type DatesAreString, type Pretty } from "~/lib/utils"; +import { Checkbox } from "~/components/ui/checkbox" +import MDEditor from '@uiw/react-md-editor' +import { useTheme } from "next-themes" +import type { Entries, IterableElement } from 'type-fest'; +import type { RouterOutputs } from '~/server/routers/_app'; +import DependentText from '~/app/_components/DependentText'; +import DependentFormMessaage from '~/app/_components/MutationFormMessage'; +import { useEffect, useState } from 'react'; +import SelectFormField from '~/app/_components/SelectFormField'; +import TexttInputFormField from '~/app/_components/TextInputFormField'; +import MdeFormField from '~/app/_components/MdeFormField'; +import CalendarFormField from '~/app/_components/CalenderFormField'; + +export default function CreateUpdateCvEntryForm(params: { className?: string, entry?: IterableElement, isUpdate?: boolean }) { + const [isUpdate, setIsUpdate] = useState(params.isUpdate ? params.isUpdate : (params.entry ? true : false)) + const [categoryId,setCategoryId] = useState() + const [categoryName,setCategoryName] = useState() + const { theme } = useTheme() + const schemas = entitySchemas('cvEntry') + const {data:categories,isSuccess: categoriesSuccess} = trpc.category.select.useQuery({}) + useEffect(() => { + if (isUpdate) { + return + } + setCategoryId(categories?.at(0)?.id) + if (categories !== undefined && categories[0]?.name !== null) { + setCategoryName(categories[0]?.name) + } + },[categoriesSuccess]) + let defaultValues = { + id: params.entry ? params.entry.id : crypto.randomUUID(), + title: params.entry ? params.entry.title : "", + description: params.entry ? params.entry.description : "", + categoryId: params.entry ? params.entry.categoryId : categoriesSuccess ? categories.at(0)?.id : "", + fromTime: params.entry ? ft(params.entry).fromTime : new Date(), + toTime: params.entry ? tt(params.entry).toTime : new Date(), + hideDates: params.entry ? params.entry.hideDates : false, + }; + const form = useForm>({ + resolver: zodResolver(schemas.insert), + defaultValues: defaultValues, + // values: params.entry ? ut(ct(tt(ff(params.entry)))) : defaultValues + }) + const createMutation = trpc.entry.insert.useMutation({onSuccess: makeOnSuccess('create',form,setIsUpdate)}) + const updateMutation = trpc.entry.update.useMutation({onSuccess: makeOnSuccess('update',form)}) + function onSubmit(values: z.infer) { + isUpdate ? + updateMutation.mutate(values) : + createMutation.mutate(values) + } + //TODO use SelectFormField and TextInputFormField + return ( + + + + + + + +
+ + + { + categoriesSuccess ? + <> + {categories.map((c) => { + return ( {c.name} ) + })} + : + + } + + + + + + ( + + Hide dates + + + + + )} + /> + + + + +
+
+ ) +} diff --git a/src/app/admin/cv/entry/_components/UpdateForm.tsx b/src/app/admin/cv/entry/_components/UpdateForm.tsx deleted file mode 100644 index 2bb1a36..0000000 --- a/src/app/admin/cv/entry/_components/UpdateForm.tsx +++ /dev/null @@ -1,275 +0,0 @@ -'use client' -import { updateRouteSchema, updateRouteSchemaCliennt } from "~/lib/schema/cv/entry" -import { zodResolver } from '@hookform/resolvers/zod' -import { useForm } from 'react-hook-form' -import { z } from "zod"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form"; -import { Input } from "~/components/ui/input"; -import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~/components/ui/select"; -import { trpc } from "~/app/_trpc/Client"; -import { Button } from "~/components/ui/button"; -import * as Card from '~/components/ui/card' -import { format } from 'date-fns' -import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover"; -import { cn, type Element } from "~/lib/utils"; -import { CalendarIcon, Delete } from "lucide-react"; -import { Calendar } from "~/components/ui/calendar"; -import { Textarea } from "~/components/ui/textarea"; -import { usePathname, useRouter } from "next/navigation"; -import { Checkbox } from "~/components/ui/checkbox"; -import MDEditor from '@uiw/react-md-editor' -import { useTheme } from "next-themes" -import type { EntryRouterOutputs } from "~/server/routers/cv/entry"; - -export default function UpdateCvEntryForm(params : { id: string, className: string | undefined }) { - const { theme } = useTheme() - const id = params.id - const categories = trpc.cv.category.list.useQuery() - const entry = trpc.cv.entry.get.useQuery({ id: id }) - const router = useRouter(); - const pathname = usePathname(); - const form = useForm>({ - resolver: zodResolver(updateRouteSchemaCliennt), - defaultValues: { - by: { id: id }, - update: { - categoryId: entry.data?.categoryId, - title: entry.data?.title, - description: entry.data?.description, - } - } - }) - const updateFormValues = (v:EntryRouterOutputs['get']|Element) => { - form.setValue('update.title', v?.title) - form.setValue('update.description', v?.description); - form.setValue('update.fromTime', new Date(Date.parse(v ? v.fromTime : ""))) - form.setValue('update.toTime', new Date(Date.parse(v ? v.toTime : ""))) - form.setValue('update.categoryId', v?.categoryId) - form.setValue('update.hideDates', v?.hideDates) - } - entry.promise.then((v) => { - updateFormValues(v) - }) - const updateMutation = trpc.cv.entry.update.useMutation({ - onSuccess: (v) => { - if (v[0] !== undefined) { - updateFormValues(v[0]) - } - } - }) - const deleteMutation = trpc.cv.entry.delete.useMutation({ - onSuccess: () => { - if (!pathname.includes('list')) { - router.back() - } - } - }) - const deleteEntry = (id: string) => { - deleteMutation.mutate(id) - } - function onSubmit(values: z.infer) { - let { title, categoryId, description, hideDates } = values.update; - updateMutation.mutate({ - by: { id: id }, - update: { - fromTime: format(values.update.fromTime, 'yyyy-MM-dd'), - toTime: format(values.update.toTime, 'yyyy-MM-dd'), - title: title, - categoryId: categoryId, - description: description, - hideDates: hideDates - } - }) - } - if (entry.data !== undefined) { - return ( - - - - Create Entry - - - -
- - ( - - - Category - - { - categories.data ? ( - - ) : - } - - )} - /> - ( - - - Title - - - - - - )} - /> - ( - - - Description - - - - - - )} - /> - ( - - From Date - - - - - - - - - date > new Date() || date < new Date("1900-01-01") - } - initialFocus - /> - - - - - )} - /> - ( - - To Date - - - - - - - - - date > new Date() || date < new Date("1900-01-01") - } - initialFocus - /> - - - - - )} - /> - ( - - Hide dates - - - - - )} - /> - - - {updateMutation.error ? updateMutation.error.message : updateMutation.status} - - - - -
-
- ) - } else { - return ( - <> - - ) - } -} diff --git a/src/app/admin/cv/entry/create/page.tsx b/src/app/admin/cv/entry/create/page.tsx index 65c5f46..fd4e558 100644 --- a/src/app/admin/cv/entry/create/page.tsx +++ b/src/app/admin/cv/entry/create/page.tsx @@ -1,8 +1,8 @@ 'use client' -import CreateCvEntryForm from "../_components/CreateForm" +import CreateUpdateCvEntryForm from "../_components/CreateUpdateForm" export default function Page() { return ( - + ) } diff --git a/src/app/admin/cv/entry/list/page.tsx b/src/app/admin/cv/entry/list/page.tsx index 184b23d..ed9622c 100644 --- a/src/app/admin/cv/entry/list/page.tsx +++ b/src/app/admin/cv/entry/list/page.tsx @@ -7,19 +7,19 @@ import * as Card from '~/components/ui/card' import { useGsapContext } from "~/app/_providers/GsapProvicer"; export default function CvPage() { - const cvEntries = trpc.cv.entry.list.useQuery(); + const entires = trpc.entry.select.useQuery({}); const gsap = useGsapContext() const container = useRef(null); useGSAP(() => { gsap?.from('.gsapan', { x: -100, opacity: 0, duration: 0.5, stagger: { each: 0.3 } }) - }, { scope: container, dependencies: [cvEntries.status], revertOnUpdate: true }); + }, { scope: container, dependencies: [entires.status], revertOnUpdate: true }); return (
- {cvEntries.data == undefined ? + {entires.data == undefined ?
: <> - {cvEntries.data.map((ent) => { + {entires.data.map((ent) => { return ( @@ -31,9 +31,8 @@ export default function CvPage() {
- Category id : {ent.id}
- Category Name : - {ent.category?.name} + Category: + {ent.categoryId}
diff --git a/src/app/admin/project/_components/CollapsibleForm.tsx b/src/app/admin/project/_components/CollapsibleForm.tsx index 73704fd..f2b9bd4 100644 --- a/src/app/admin/project/_components/CollapsibleForm.tsx +++ b/src/app/admin/project/_components/CollapsibleForm.tsx @@ -2,9 +2,9 @@ import { ChevronsUpDown, Plus } from "lucide-react" import { Button } from "~/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible"; import { type Element } from "~/lib/utils"; -import CreateUpdateProjectForm from "./CreateForm"; -import type { ProjectRouterOutputs } from "~/server/routers/project"; -export default function CollapsibleForm(params:{project:Element|undefined}) { +import CreateUpdateProjectForm from "./CreateUpdateProjectForm"; +import type { RouterOutputs } from "~/server/routers/_app"; +export default function CollapsibleForm(params:{project?:Element}) { return ( @@ -18,7 +18,7 @@ export default function CollapsibleForm(params:{project:Element : - + } diff --git a/src/app/admin/project/_components/CreateUpdateProjectForm.tsx b/src/app/admin/project/_components/CreateUpdateProjectForm.tsx index 6390088..a45a7b9 100644 --- a/src/app/admin/project/_components/CreateUpdateProjectForm.tsx +++ b/src/app/admin/project/_components/CreateUpdateProjectForm.tsx @@ -1,5 +1,4 @@ "use client" -import { insertSchema } from "~/lib/schema/project/project" import { zodResolver } from '@hookform/resolvers/zod' import { useForm } from 'react-hook-form' import { z } from "zod"; @@ -9,41 +8,56 @@ import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~ import { trpc } from "~/app/_trpc/Client"; import { Button } from "~/components/ui/button"; import * as Card from '~/components/ui/card' -import type { Entries } from 'type-fest' -import { type Element } from "~/lib/utils"; -import type { ProjectRouterOutputs } from "~/server/routers/project"; -import { Suspense } from "react"; -export default function CreateUpdateProjectForm(params:{className?:string, project?:Element}) { - const [techStacks,] = trpc.project.stack.list.useSuspenseQuery() - const form = useForm>({ - resolver: zodResolver(insertSchema), +import type { Entries, IterableElement } from 'type-fest' +import { entitySchemas, makeOnSuccess } from "~/lib/utils"; +import { Suspense, useEffect, useState } from "react"; +import type { RouterOutputs } from '~/server/routers/_app'; +import TexttInputFormField from '~/app/_components/TextInputFormField'; +import SelectFormField from '~/app/_components/SelectFormField'; +import DependentFormMessaage from '~/app/_components/MutationFormMessage'; +import DependentText from '~/app/_components/DependentText'; +export default function CreateUpdateProjectForm(params: { className?: string, project?: IterableElement, isUpdate?: boolean }) { + const [isUpdate, setIsUpdate] = useState(params.isUpdate ? params.isUpdate : (params.project ? true : false)) + const [stackId, setstackId] = useState() + const [stackName, setstackName] = useState() + const schemas = entitySchemas('project') + const { data: stacks, isSuccess: stacksSuccess } = trpc.techStack.select.useQuery({}) + useEffect(() => { + if (isUpdate) { + return + } + setstackId(stacks?.at(0)?.id) + if (stacks !== undefined) { + setstackName(stacks.at(0)?.stackItems ? stacks.at(0)?.stackItems?.join("-") : "") + } + }, [stacksSuccess]) + const form = useForm>({ + resolver: zodResolver(schemas.insert), defaultValues: { - id: params.project ? params.project.id : crypto.randomUUID(), + id: params.project ? params.project.id : crypto.randomUUID(), + stackId: params.project ? params.project.stackId : stacksSuccess ? stacks?.at(0)?.id : "", + releaseStatus: params.project ? params.project.releaseStatus : "unreleased", + releaseLink: params.project ? params.project.releaseLink : "", + sourceType: params.project ? params.project.sourceType : "open", + sourceLink: params.project ? params.project.sourceLink : "" } }) - const createMutation = trpc.project.create.useMutation({ - onSuccess: (data) => { form.setValue("id", data[0] ? data[0].id : "") } + const createMutation = trpc.project.insert.useMutation({ + onSuccess: makeOnSuccess('create',form,setIsUpdate) }) const updateMutation = trpc.project.update.useMutation({ - onSuccess: (data) => { - if (data.length > 0 && data[0] !== undefined) { - let entries = Object.entries(data[0]) as Entries - entries.forEach( (entry) => { - form.setValue(entry[0],entry[1]) - }) - } - } + onSuccess: makeOnSuccess('update',form) }) - function onSubmit(values: z.infer) { + function onSubmit(values: z.infer) { params.project ? - updateMutation.mutate({by: {id: values.id}, update: { ...values}}) : + updateMutation.mutate(values) : createMutation.mutate(values) } return ( - Create Entry + @@ -52,99 +66,32 @@ export default function CreateUpdateProjectForm(params:{className?:string, proje onSubmit={form.handleSubmit(onSubmit)} className="space-y-8" > - ( - - - Stack - - - - )} - /> - ( - - - Title - - - - - - )} - /> - ( - - - Source Type - - - - )} - /> - ( - - Release Status - - - )} - /> - - - { params.project ? - ( - <>{updateMutation.error ? updateMutation.error.message : updateMutation.status} - ) : - ( - <>{createMutation.error ? createMutation.error.message : createMutation.status} - ) - + + { + stacksSuccess ? + <> + {stacks.map((stack) => { + return ( {stack.stackItems ? stack.stackItems.join("-") : "Empty Stack"} ) + })} + : + } - + + + + open + closed + + + + released + unreleased + + + + diff --git a/src/app/admin/project/techStack/_components/CreateUpdateForm.tsx b/src/app/admin/project/techStack/_components/CreateUpdateForm.tsx index 2946ada..c501082 100644 --- a/src/app/admin/project/techStack/_components/CreateUpdateForm.tsx +++ b/src/app/admin/project/techStack/_components/CreateUpdateForm.tsx @@ -1,5 +1,4 @@ "use client" -import { insertSchema } from "~/lib/schema/project/techStack" import { zodResolver } from '@hookform/resolvers/zod' import { useForm } from 'react-hook-form' import { z } from "zod"; @@ -8,36 +7,42 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from " import { trpc } from "~/app/_trpc/Client"; import { Button } from "~/components/ui/button"; import * as Card from '~/components/ui/card' -import type { Entries } from 'type-fest' -import { type Element } from "~/lib/utils"; -import type { StackRouter, StackRouterOutputs } from "~/server/routers/project/techStack"; +import type { Entries, IterableElement } from 'type-fest' import { stackItemEnum } from "~/server/db/schema"; import { ToggleGroup, ToggleGroupItem } from "~/components/ui/toggle-group"; +import type { RouterOutputs } from '~/server/routers/_app'; +import { entitySchemas } from '~/lib/utils'; +import { useState } from 'react'; -export default function CreateUpdateForm(params:{className:string|undefined, techStack?:Element}) { - const form = useForm>({ - resolver: zodResolver(insertSchema), +export default function CreateUpdateStackForm(params:{className?:string, techStack?:IterableElement,isUpdate?: boolean }) { + const schemas = entitySchemas('techStack') + const form = useForm>({ + resolver: zodResolver(schemas.insert), defaultValues: { id: params.techStack ? params.techStack.id : crypto.randomUUID(), stackItems: params.techStack ? params.techStack.stackItems : [], } }) - const createMutation = trpc.project.stack.create.useMutation({ - onSuccess: (data) => { form.setValue("id", data[0] ? data[0].id : "") } + const [isUpdate, setIsUpdate] = useState(params.isUpdate ? params.isUpdate : (params.techStack ? true : false)) + const createMutation = trpc.techStack.insert.useMutation({ + onSuccess: (data) => { + form.setValue("id", data[0] ? data[0].id : ""); + setIsUpdate(true) + } }) - const updateMutation = trpc.project.stack.update.useMutation({ + const updateMutation = trpc.techStack.update.useMutation({ onSuccess: (data) => { if (data.length > 0 && data[0] !== undefined) { let entries = Object.entries(data[0]) as Entries - entries.forEach( (entry) => { - form.setValue(entry[0],entry[1]) + entries.forEach((entry) => { + form.setValue(entry[0], entry[1]) }) } } }) - function onSubmit(values: z.infer) { + function onSubmit(values: z.infer) { params.techStack ? - updateMutation.mutate({by: {id: values.id}, update: { ...values}}) : + updateMutation.mutate(values) : createMutation.mutate(values) } return ( diff --git a/src/lib/schema/cv/category.ts b/src/lib/schema/cv/category.ts deleted file mode 100644 index 92b29a9..0000000 --- a/src/lib/schema/cv/category.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { cvCategory } from "~/server/db/schema"; -import { createInsertSchema, createUpdateSchema, createSelectSchema} from 'drizzle-zod' -import { z } from "zod"; -export const selectSchema = createSelectSchema(cvCategory); -export const insertSchema = createInsertSchema(cvCategory); -export const updateSchema = createUpdateSchema(cvCategory); -export const getSchema = selectSchema.pick({id: true}); -export const updateRouteSchema = z.object({ - by: selectSchema.pick({id:true}), - update: updateSchema - }) diff --git a/src/lib/schema/cv/entry.ts b/src/lib/schema/cv/entry.ts deleted file mode 100644 index 2cc30af..0000000 --- a/src/lib/schema/cv/entry.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { cvEntry } from "~/server/db/schema"; -import { createInsertSchema, createUpdateSchema, createSelectSchema} from 'drizzle-zod' -import { z } from "zod"; -export const selectSchema = createSelectSchema(cvEntry); -export const insertSchema = createInsertSchema(cvEntry); -export const inserSchemaNoDates = insertSchema.omit({ - fromTime:true, - toTime:true -}) -export const insertSchemaForm = inserSchemaNoDates.merge(z.object({ - fromTime: z.date(), - toTime: z.date() -})) -export const updateSchema = createUpdateSchema(cvEntry); -export const updateSchemaNoDates = updateSchema.omit({ - fromTime:true, - toTime:true -}) -export const getSchema = selectSchema.pick({id: true}); -export const updateRouteSchema = z.object({ - by: selectSchema.pick({id:true}), - update: updateSchema.omit({id:true}) -}) -export const updateRouteSchemaCliennt = z.object({ - by: selectSchema.pick({id:true}), - update: updateSchemaNoDates.merge(z.object({ - fromTime: z.date(), - toTime: z.date() - })) - }) diff --git a/src/lib/schema/project/project.ts b/src/lib/schema/project/project.ts deleted file mode 100644 index 081561f..0000000 --- a/src/lib/schema/project/project.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { project } from "~/server/db/schema" -import { createInsertSchema, createUpdateSchema, createSelectSchema} from 'drizzle-zod' -import { z } from "zod"; -export const selectSchema = createSelectSchema(project); -export const insertSchema = createInsertSchema(project); -export const updateSchema = createUpdateSchema(project); -export const getSchema = selectSchema.pick({id: true}); -export const updateRouteSchema = z.object({ - by: selectSchema.pick({id:true}), - update: updateSchema - }) diff --git a/src/lib/schema/project/techStack.ts b/src/lib/schema/project/techStack.ts deleted file mode 100644 index de65e35..0000000 --- a/src/lib/schema/project/techStack.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { techStack } from "~/server/db/schema" -import { createInsertSchema, createUpdateSchema, createSelectSchema} from 'drizzle-zod' -import { z } from "zod"; -export const selectSchema = createSelectSchema(techStack); -export const insertSchema = createInsertSchema(techStack); -export const updateSchema = createUpdateSchema(techStack); -export const getSchema = selectSchema.pick({id: true}); -export const updateRouteSchema = z.object({ - by: selectSchema.pick({id:true}), - update: updateSchema - }) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index ea10c5e..fd081ec 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,30 +1,67 @@ import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" -import { createInsertSchema, createUpdateSchema, createSelectSchema, type BuildSchema } from 'drizzle-zod' +import { createSchemaFactory, createSelectSchema, type BuildSchema } from 'drizzle-zod' import * as schema from "~/server/db/schema"; -import { PgColumn, PgTable } from "drizzle-orm/pg-core"; -import type { Entries } from "type-fest"; -import z from "zod"; -import type { Prettify } from "node_modules/zod/v4/core/util.cjs"; -import type { ExtractTablesWithRelations } from "drizzle-orm"; - +import { PgTable } from "drizzle-orm/pg-core"; +import type { FieldValues, Path, UseFormReturn } from "react-hook-form"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } -export type Element | undefined | null> = T extends Iterable ? E : never; - export type Schema = typeof schema; export type SchemaKeys = { [K in keyof S]: S[K] extends PgTable ? K : never }[keyof S] -export function entitySchemas>(table: T): { - insert: BuildSchema<'insert', Schema[T]['_']['columns'], undefined> - update: BuildSchema<'update', Schema[T]['_']['columns'], undefined> - select: BuildSchema<'select', Schema[T]['_']['columns'], undefined> -} { +export type Pretty = { + [K in keyof T]: T[K]; +} & {} + +const { createInsertSchema, createUpdateSchema } = createSchemaFactory({ + coerce: { + date: true + } +}) + +export type DatesAreString = { + [K in keyof T] : T[K] extends Date ? string : T[K] +} + +// type X = { +// foo: Date +// bar: number +// } + +// type Y = DatesAreString + +export function makeOnSuccess(uc: 'update' | 'create', form: UseFormReturn, setIsUpdate?: (arg0: boolean) => void) { + switch (uc) { + case 'update': + return (data: T[]) => { + if (data.length > 0 && data[0] !== undefined) { + for (let k in data[0]) { + form.setValue((k as unknown as Path), data[0][k]) + } + } + } + case 'create': + if (setIsUpdate !== undefined) { + return (data: T[]) => { + form.setValue(("id" as Path), data[0] ? data[0].id : ""); + setIsUpdate(true) + } + } + } +} + +export function entitySchemas>(table: T) + // : { + // insert: Pretty> + // update: Pretty> + // select: Pretty> + // } +{ const insertSchema = createInsertSchema(schema[table]); const updateSchema = createUpdateSchema(schema[table]); const selectSchema = createSelectSchema(schema[table]); @@ -34,3 +71,109 @@ export function entitySchemas>(table: T): { select: selectSchema, } } + +// type StringToDateMapped> = Omit & Record + +// export function stringToDates>( +// obj: T, +// key: K +// ): Pretty> { +// return { +// ...obj, +// [key] : new Date(obj[key] as string) +// } as StringToDateMapped; +// } + + +// aproach above works but gets verbose to use + +// functional concept: +// +// function curryConverter(key?,converter) { +// if key is undefined { +// return converter +// } +// new newConverter = (object) => { +// object = converter(object) +// convertOpereration(key,object) +// } +// return function (key) => { +// if key is undefined { +// curryConverter(key,newConverter) +// } else { +// return newConverter +// } +// } +// } +// +// curryConverter('nextKey',objectStrongToDateConverterCreator('prevKey'))('veryNextKey')() +// - key is defined +// - new converter created that applies previous converter +// - function is returned that accepts key +// - function gets called with veryNextKey at which point curryConverter gets Called again +// - since key is defined the cycle repeats once more +// - a function is returned that accepts key +// - no key is provided so the newConveerter from previous cycle gets returned +// - sadly i believe the type inference wont work but i will try +// - i was right it is not possible to corretly get return type inference for anything recursive no matter how hard you try to hack it :( +// - see: https://github.com/microsoft/TypeScript/issues/3336 +// - leaving this as comment as a cautionary tale so i wont get lost in the sauce again +// + +// type StDRecord> = Pretty & Record> +// type Converter = >(obj: T) => StDRecord +// type CurriedConverter = (converter:Converter,key?: K) => K extends string ? +// CurriedConverter : +// Converter + +// function curriedConverter(converter: Converter): Converter; +// function curriedConverter( +// converter: Converter, +// key: K +// ): (k?: string) => Pretty>>; +// // Implementation +// function curriedConverter( +// converter: Converter, +// key?: K +// ): unknown { +// if (key === undefined) { +// return converter; +// } +// let newConverter = >(obj: T): Pretty & Record> => { +// let updated = converter(obj as Record); +// return { +// ...updated, +// [key]: new Date(updated[key as unknown as keyof typeof updated]) +// } as any; +// }; +// return function (nk?: string) { +// if (nk == undefined) { +// return curriedConverter(newConverter,key); +// } +// return newConverter; +// } +// } + +function objectStringToDateConverterCreator(key: K) { + return function >(obj: T): Omit & Record { + if (!(key in obj)) { + return obj + } + if (typeof obj[key] !== 'string') { + return obj + } + return { + ...obj, + [key]: new Date(obj[key]) + } as any; + }; +} +type StrToDatesObj = { fromTime: string | null, toTime: string | null, createdAt: string | null, updatedAt: string | null } +export const ft = objectStringToDateConverterCreator('fromTime') +export const tt = objectStringToDateConverterCreator('toTime') +export const ct = objectStringToDateConverterCreator('createdAt') +export const ut = objectStringToDateConverterCreator('updatedAt') +export const strsToDates = (obj: Pretty) => { + return ft(tt(ct(ut(obj)))) +} + diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index 07ce6ca..806f9bc 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -33,16 +33,16 @@ export const cvEntry = createTable( (d) => ({ id: d.uuid().primaryKey().notNull(), categoryId: d.uuid('category_id'), - fromTime: d.date().notNull(), - toTime: d.date().notNull(), + fromTime: d.timestamp().notNull().$type(), + toTime: d.timestamp().notNull().$type(), title: d.varchar({length:50}).notNull(), description: d.text(), hideDates: d.boolean(), createdAt: d .timestamp({ withTimezone: true }) .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - updatedAt: d.timestamp({ withTimezone: true }).$onUpdate(() => new Date()), + .notNull().$type(), + updatedAt: d.timestamp({ withTimezone: true }).$onUpdate(() => new Date()).$type(), }) ) diff --git a/src/server/lib.ts b/src/server/lib.ts index d204726..d7aece6 100644 --- a/src/server/lib.ts +++ b/src/server/lib.ts @@ -6,16 +6,13 @@ import { isAdmin } from "~/app/actions"; import { TRPCError } from "@trpc/server"; import { entitySchemas, type Schema, type SchemaKeys } from "~/lib/utils"; import { eq, and } from "drizzle-orm"; - - - - - +import z from "zod"; function isKeyOf(k: any, obj: T): k is keyof T { return k in obj } +// this sucks should have never refactored to it export function trpcCrudRouterFromDrizzleEntity>(table: T) { const schemas = entitySchemas(table) if (!isKeyOf(table, schema)) { @@ -46,19 +43,14 @@ export function trpcCrudRouterFromDrizzleEntity>(ta ) } const { input } = opts; + console.log(input) if (input === undefined) { throw new TRPCError({ message: "no update input", code: "BAD_REQUEST" }) } - const conds = (Object.entries(input) as Entries).map(([k, v]) => { - if (!isKeyOf(k, schema[table])) { - throw new TRPCError({ message: "Invalid key for column", code: "BAD_REQUEST" }); - } - return eq(schema[table][k], v) - }) - if (conds.length > 0) { - return await db.update(schema[table]).set(input).where(and(...conds)).returning().execute(); + if (!isKeyOf('id',input) || !isKeyOf('id',schema[table])) { + throw new TRPCError({ message: "no id provided", code: "BAD_REQUEST" }) } - throw new TRPCError({ message: "trying to update all entities", code: "BAD_REQUEST" }) + 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) => { let admin = await isAdmin(); @@ -86,7 +78,7 @@ export function trpcCrudRouterFromDrizzleEntity>(ta return eq(schema[table][k], v) }) if (conds.length > 0) { - return await db.update(schema[table]).where(and(...conds)).returning().execute(); + return await db.delete(schema[table]).where(and(...conds)).returning().execute(); } throw new TRPCError({ message: "trying to delete all entities", code: "BAD_REQUEST" }) }), diff --git a/src/server/routers/_app.ts b/src/server/routers/_app.ts index 8e14f84..3eea2c5 100644 --- a/src/server/routers/_app.ts +++ b/src/server/routers/_app.ts @@ -1,12 +1,19 @@ import type { inferRouterOutputs } from "@trpc/server"; import { router } from "../trpc"; -import { CvRouter } from "./cv"; import type { inferReactQueryProcedureOptions } from "@trpc/react-query"; -import { ProjectRouter } from "./project"; +import { trpcCrudRouterFromDrizzleEntity } from "../lib"; +import type { inferRouterMeta } from "@trpc/server/unstable-core-do-not-import"; +const { router : project } = trpcCrudRouterFromDrizzleEntity('project') +const { router : techStack } = trpcCrudRouterFromDrizzleEntity('techStack') +const { router : category } = trpcCrudRouterFromDrizzleEntity('cvCategory') +const { router : entry } = trpcCrudRouterFromDrizzleEntity('cvEntry') +const root = {} export const trpcRouter = router({ - cv: CvRouter, - project: ProjectRouter + project: project, + techStack: techStack, + category: category, + entry: entry }) export type TrpcRouter = typeof trpcRouter diff --git a/src/server/routers/cv/category.ts b/src/server/routers/cv/category.ts deleted file mode 100644 index 477c605..0000000 --- a/src/server/routers/cv/category.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { db } from "~/server/db"; -import { publicProcedure, router } from "~/server/trpc"; -import { cvCategory, cvEntry } from "~/server/db/schema"; -import { eq } from "drizzle-orm"; -import { isAdmin } from "~/app/actions"; - -import * as Schemas from "~/lib/schema/cv/category" -import { TRPCError, type inferRouterOutputs } from "@trpc/server"; -import { z } from "zod"; - -export const CategoryRouter = router({ - list: publicProcedure.query(async () => { - const categories = await db.query.cvCategory.findMany({ - orderBy: (model, { desc }) => desc(model.name), - with: { - cvEntry: true - } - }); - return categories; - }), - get: publicProcedure.input(Schemas.getSchema).query(async (opts) => { - const { input } = opts - const categories = await db.query.cvCategory.findMany({ - with: { - cvEntry: true - }, - where: eq(cvCategory.id, input.id), - limit: 1 - }) - if (categories[0] !== undefined) { - return categories[0] - } else { - return null - } - }), - delete: publicProcedure.input(z.string()).mutation(async (opts) => { - let admin = await isAdmin() - if (!admin) { - throw new TRPCError( - { message: "Access denied", code: "FORBIDDEN", } - ) - } - const { input } = opts; - db.delete(cvCategory).where(eq(cvCategory.id,input)).execute() - }), - create: publicProcedure.input(Schemas.insertSchema).mutation(async (opts) => { - let admin = await isAdmin() - if (!admin) { - throw new TRPCError( - { message: "Access denied", code: "FORBIDDEN", } - ) - } - const { input } = opts; - const category = await db.insert(cvCategory).values(input).returning().execute() - return category - }), - update: publicProcedure - .input(Schemas.updateRouteSchema) - .mutation(async (opts) => { - let admin = await isAdmin() - if (!admin) { - throw new TRPCError( - { message: "Access denied", code: "FORBIDDEN", } - ) - } - const { input } = opts; - const category = await db.update(cvCategory) - .set(input.update) - .returning() - .where(eq(cvCategory.id, input.by.id)) - return category - }) -}) - -export type CategoryRouterOutputs = inferRouterOutputs diff --git a/src/server/routers/cv/entry.ts b/src/server/routers/cv/entry.ts deleted file mode 100644 index bbd1eec..0000000 --- a/src/server/routers/cv/entry.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { db } from "~/server/db"; -import { publicProcedure, router } from "~/server/trpc"; -import { cvEntry } from "~/server/db/schema"; -import { eq } from "drizzle-orm"; -import { isAdmin } from "~/app/actions"; - -import * as Schemas from "~/lib/schema/cv/entry" -import { TRPCError, type inferRouterOutputs } from "@trpc/server"; -import { z } from "zod"; - -export const EntryRouter = router({ - list: publicProcedure.query(async () => { - const entries = await db.query.cvEntry.findMany({ - orderBy: (model, { desc }) => desc(model.toTime), - with: { - category: true - } - }); - return entries; - }), - get: publicProcedure.input(Schemas.getSchema).query(async (opts) => { - const { input } = opts - const entries = await db.query.cvEntry.findMany({ - with: { - category: true - }, - where: eq(cvEntry.id, input.id), - limit: 1 - }) - if (entries[0] !== undefined) { - return entries[0] - } else { - return null - } - }), - delete: publicProcedure.input(z.string()).mutation(async (opts) => { - let admin = await isAdmin() - if (!admin) { - throw new TRPCError( - { message: "Access denied", code: "FORBIDDEN", } - ) - } - const { input } = opts; - db.delete(cvEntry).where(eq(cvEntry.id,input)).execute() - }), - create: publicProcedure.input(Schemas.insertSchema).mutation(async (opts) => { - let admin = await isAdmin() - if (!admin) { - throw new TRPCError( - { message: "Access denied", code: "FORBIDDEN", } - ) - } - const { input } = opts; - return await db.insert(cvEntry).values(input).returning().execute() - }), - update: publicProcedure - .input(Schemas.updateRouteSchema) - .mutation(async (opts) => { - let admin = await isAdmin() - if (!admin) { - throw new TRPCError( - { message: "Access denied", code: "FORBIDDEN", } - ) - } - const { input } = opts; - const entry = await db.update(cvEntry) - .set(input.update) - .returning() - .where(eq(cvEntry.id, input.by.id)) - return entry - }) -}) - -export type EntryRouterOutputs = inferRouterOutputs diff --git a/src/server/routers/cv/index.ts b/src/server/routers/cv/index.ts deleted file mode 100644 index 7e72119..0000000 --- a/src/server/routers/cv/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { router } from "~/server/trpc" -import { CategoryRouter } from "./category" -import { EntryRouter } from "./entry" -import { trpcCrudRouterFromDrizzleEntity } from "~/server/lib" -import * as schema from '~/server/db/schema' - -const { router : categoryv2 } = trpcCrudRouterFromDrizzleEntity('cvCategory') -const { router : entryv2 } = trpcCrudRouterFromDrizzleEntity('cvEntry') -export const CvRouter = router({ - category: categoryv2, - entry:entryv2 -}) diff --git a/src/server/routers/project/index.ts b/src/server/routers/project/index.ts deleted file mode 100644 index 4046f38..0000000 --- a/src/server/routers/project/index.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { publicProcedure, router } from "~/server/trpc" -import { db } from "~/server/db"; -import * as Schemas from '~/lib/schema/project/project' -import { eq } from "drizzle-orm"; -import { project } from "~/server/db/schema"; -import { isAdmin } from "~/app/actions"; -import z from "zod"; -import { StackRouter } from "./techStack"; -import { TRPCError, type inferRouterOutputs} from "@trpc/server"; -import { trpcCrudRouterFromDrizzleEntity } from "~/server/lib"; -const { router : project } = trpcCrudRouterFromDrizzleEntity('project') -const { router : techStack } = trpcCrudRouterFromDrizzleEntity('techStack') -export const ProjectRouter = router({ - list: publicProcedure.query(async () => { - return await db.query.project.findMany({ - orderBy: (model, {desc}) => desc(model.title), - with: { - techStack: true - } - }); - }), - get: publicProcedure.input(Schemas.getSchema).query(async (opts) => { - const {input} = opts; - const projects = await db.query.project.findMany({ - with: { - techStack: true - }, - where: eq(project.id,input.id), - limit: 1 - }); - if (projects[0] !== undefined) { - return projects[0] - } else { - return null - } - }), - delete: publicProcedure.input(z.string()).mutation(async (opts) => { - let admin = await isAdmin() - if (!admin) { - throw new TRPCError( - { message: "Access denied", code: "FORBIDDEN", } - ) - } - const { input } = opts; - db.delete(project).where(eq(project.id,input)).execute() - }), - create: publicProcedure.input(Schemas.insertSchema).mutation(async (opts) => { - let admin = await isAdmin() - if (!admin) { - throw new TRPCError( - { message: "Access denied", code: "FORBIDDEN", } - ) - } - const { input } = opts; - return await db.insert(project).values(input).returning().execute(); - }), - update: publicProcedure - .input(Schemas.updateRouteSchema) - .mutation(async (opts) => { - let admin = await isAdmin() - if (!admin) { - throw new TRPCError( - { message: "Access denied", code: "FORBIDDEN", } - ) - } - const { input } = opts; - return await db.update(project) - .set(input.update) - .returning() - .where(eq(project.id,input.by.id)); - }), - stack: StackRouter, -}) - -export type ProjectRouterOutputs = inferRouterOutputs diff --git a/src/server/routers/project/techStack.ts b/src/server/routers/project/techStack.ts deleted file mode 100644 index e2e13ea..0000000 --- a/src/server/routers/project/techStack.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { publicProcedure, router } from "~/server/trpc" -import { db } from "~/server/db"; -import * as Schemas from '~/lib/schema/project/techStack' -import { eq } from "drizzle-orm"; -import { techStack } from "~/server/db/schema"; -import { isAdmin } from "~/app/actions"; -import { TRPCError, type inferRouterOutputs} from "@trpc/server"; -import z from "zod"; -export const StackRouter = router({ - list: publicProcedure.query(async () => { - return await db.query.techStack.findMany(); - }), - get: publicProcedure.input(Schemas.getSchema).query(async (opts) => { - const {input} = opts; - const techStacks = await db.query.techStack.findMany({ - where: eq(techStack.id,input.id), - limit: 1 - }); - if (techStacks[0] !== undefined) { - return techStacks[0] - } else { - return null - } - }), - delete: publicProcedure.input(z.string()).mutation(async (opts) => { - let admin = await isAdmin() - if (!admin) { - throw new TRPCError( - { message: "Access denied", code: "FORBIDDEN", } - ) - } - const { input } = opts; - db.delete(techStack).where(eq(techStack.id,input)).execute() - }), - create: publicProcedure.input(Schemas.insertSchema).mutation(async (opts) => { - let admin = await isAdmin() - if (!admin) { - throw new TRPCError( - { message: "Access denied", code: "FORBIDDEN", } - ) - } - const { input } = opts; - return await db.insert(techStack).values(input).returning().execute(); - }), - update: publicProcedure - .input(Schemas.updateRouteSchema) - .mutation(async (opts) => { - let admin = await isAdmin() - if (!admin) { - throw new TRPCError( - { message: "Access denied", code: "FORBIDDEN", } - ) - } - const { input } = opts; - return await db.update(techStack) - .set(input.update) - .returning() - .where(eq(techStack.id,input.by.id)) - }) -}) - -export type StackRouterOutputs = inferRouterOutputs