add react forms and basic implementation of update endpoint
This commit is contained in:
parent
6cbcc7eb21
commit
cabddd777e
14
package-lock.json
generated
14
package-lock.json
generated
@ -23,7 +23,7 @@
|
||||
"next-auth": "^4.19.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.43.9",
|
||||
"react-hook-form": "^7.44.3",
|
||||
"superjson": "1.9.1",
|
||||
"zod": "^3.20.6"
|
||||
},
|
||||
@ -4624,9 +4624,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.43.9",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.43.9.tgz",
|
||||
"integrity": "sha512-AUDN3Pz2NSeoxQ7Hs6OhQhDr6gtF9YRuutGDwPQqhSUAHJSgGl2VeY3qN19MG0SucpjgDiuMJ4iC5T5uB+eaNQ==",
|
||||
"version": "7.44.3",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.44.3.tgz",
|
||||
"integrity": "sha512-/tHId6p2ViAka1wECMw8FEPn/oz/w226zehHrJyQ1oIzCBNMIJCaj6ZkQcv+MjDxYh9MWR7RQic7Qqwe4a5nkw==",
|
||||
"engines": {
|
||||
"node": ">=12.22.0"
|
||||
},
|
||||
@ -8890,9 +8890,9 @@
|
||||
}
|
||||
},
|
||||
"react-hook-form": {
|
||||
"version": "7.43.9",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.43.9.tgz",
|
||||
"integrity": "sha512-AUDN3Pz2NSeoxQ7Hs6OhQhDr6gtF9YRuutGDwPQqhSUAHJSgGl2VeY3qN19MG0SucpjgDiuMJ4iC5T5uB+eaNQ==",
|
||||
"version": "7.44.3",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.44.3.tgz",
|
||||
"integrity": "sha512-/tHId6p2ViAka1wECMw8FEPn/oz/w226zehHrJyQ1oIzCBNMIJCaj6ZkQcv+MjDxYh9MWR7RQic7Qqwe4a5nkw==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-is": {
|
||||
|
@ -30,7 +30,7 @@
|
||||
"next-auth": "^4.19.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.43.9",
|
||||
"react-hook-form": "^7.44.3",
|
||||
"superjson": "1.9.1",
|
||||
"zod": "^3.20.6"
|
||||
},
|
||||
|
@ -1,5 +1,24 @@
|
||||
import Link from "next/link";
|
||||
|
||||
const AdminActionBody = ({
|
||||
label,
|
||||
symbol,
|
||||
}: {
|
||||
label: string;
|
||||
symbol: JSX.Element | undefined;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<span className="my-auto inline-block h-fit align-middle text-sm leading-8 text-white group-hover:text-black">
|
||||
{label}
|
||||
</span>
|
||||
<span className="inline-block align-middle text-white group-hover:text-black">
|
||||
{symbol}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const AdminActionLink = ({
|
||||
label,
|
||||
href,
|
||||
@ -13,6 +32,25 @@ const AdminActionLink = ({
|
||||
<Link
|
||||
className="py-auto group my-auto h-full space-x-2 rounded-lg border border-neutral-400 bg-neutral-800 px-2 hover:border-neutral-800 hover:bg-white"
|
||||
href={href}
|
||||
>
|
||||
<AdminActionBody label={label} symbol={symbol} />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const AdminActionButton = ({
|
||||
label,
|
||||
onClick,
|
||||
symbol,
|
||||
}: {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
symbol: JSX.Element | undefined;
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
className="py-auto group my-auto h-full space-x-2 rounded-lg border border-neutral-400 bg-neutral-800 px-2 hover:border-neutral-800 hover:bg-white"
|
||||
onClick={onClick}
|
||||
>
|
||||
<span className="my-auto inline-block h-fit align-middle text-sm leading-8 text-white group-hover:text-black">
|
||||
{label}
|
||||
@ -20,8 +58,8 @@ const AdminActionLink = ({
|
||||
<span className="inline-block align-middle text-white group-hover:text-black">
|
||||
{symbol}
|
||||
</span>
|
||||
</Link>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export { AdminActionLink };
|
||||
export { AdminActionLink, AdminActionButton };
|
||||
|
@ -1,9 +1,4 @@
|
||||
import {
|
||||
PaymentType,
|
||||
type AuditoryResource,
|
||||
SkillLevel,
|
||||
Skill,
|
||||
} from "@prisma/client";
|
||||
import { PaymentType, SkillLevel, Skill } from "@prisma/client";
|
||||
import Image from "next/image";
|
||||
import { PencilSquareIcon } from "@heroicons/react/24/solid";
|
||||
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
||||
@ -17,6 +12,10 @@ import {
|
||||
import { InfoInputLine } from "~/components/forms/textInput";
|
||||
import { PriceIcon } from "~/prices/Icons";
|
||||
import { useState } from "react";
|
||||
import { type UseFormRegister } from "react-hook-form";
|
||||
import { type RouterInputs } from "~/utils/api";
|
||||
|
||||
export type ResourceUpdateInput = RouterInputs["auditoryResource"]["update"];
|
||||
|
||||
/**
|
||||
* Renders the image selector for resource form.
|
||||
@ -109,11 +108,13 @@ const PaymentTypeOption = ({
|
||||
/**
|
||||
* Resource summary inputs - ie description, manufacturer, etc.
|
||||
*/
|
||||
const ResourceSummarySubForm = ({
|
||||
function ResourceSummarySubForm({
|
||||
register,
|
||||
resource,
|
||||
}: {
|
||||
resource?: AuditoryResource;
|
||||
}) => {
|
||||
register: UseFormRegister<ResourceUpdateInput>;
|
||||
resource?: ResourceUpdateInput;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-4 px-4">
|
||||
<div className="flex flex-row space-x-4 sm:mt-4">
|
||||
@ -144,7 +145,7 @@ const ResourceSummarySubForm = ({
|
||||
<MultiSelector
|
||||
label="Price Category"
|
||||
defaultValue={
|
||||
resource?.payment_options.toString() ?? PaymentType.FREE.toString()
|
||||
resource?.payment_options?.toString() ?? PaymentType.FREE.toString()
|
||||
}
|
||||
>
|
||||
<PaymentTypeOption type={PaymentType.FREE} label="Free" />
|
||||
@ -189,12 +190,14 @@ const ResourceSummarySubForm = ({
|
||||
</MultiSelectorMany>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const ResourceDescriptionSubForm = ({
|
||||
register,
|
||||
resource,
|
||||
}: {
|
||||
resource?: AuditoryResource;
|
||||
register: UseFormRegister<ResourceUpdateInput>;
|
||||
resource?: ResourceUpdateInput;
|
||||
}) => {
|
||||
const [dropdownOpen, toggleDropdown] = useState(false);
|
||||
|
||||
@ -203,6 +206,7 @@ const ResourceDescriptionSubForm = ({
|
||||
<label className="text-md font-semibold">Description</label>
|
||||
<div className="relative mt-4 overflow-hidden rounded-xl border border-neutral-400 bg-neutral-200 text-left shadow">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
toggleDropdown(!dropdownOpen);
|
||||
}}
|
||||
@ -217,28 +221,34 @@ const ResourceDescriptionSubForm = ({
|
||||
<ChevronDownIcon className="mx-2 my-auto w-4 text-white group-hover:animate-bounce" />
|
||||
</button>
|
||||
<textarea
|
||||
{...register("description", { required: true })}
|
||||
className={
|
||||
"h-48 w-full rounded-b-xl p-2" + (dropdownOpen ? " hidden" : "")
|
||||
}
|
||||
>
|
||||
{resource?.description}
|
||||
</textarea>
|
||||
/>
|
||||
<textarea
|
||||
{...register("manufacturer.notice")}
|
||||
className={
|
||||
"h-48 w-full rounded-b-xl bg-neutral-800 p-2 text-white" +
|
||||
(dropdownOpen ? "" : " hidden")
|
||||
}
|
||||
>
|
||||
{resource?.manufacturer?.notice}
|
||||
</textarea>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ResourceForm = ({ resource }: { resource?: AuditoryResource }) => {
|
||||
const ResourceForm = ({
|
||||
resource,
|
||||
register,
|
||||
error,
|
||||
}: {
|
||||
resource?: ResourceUpdateInput;
|
||||
register: UseFormRegister<ResourceUpdateInput>;
|
||||
error?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className="mx-auto flex max-w-2xl flex-col flex-col-reverse py-1 sm:flex-row sm:divide-x sm:py-4">
|
||||
<form className="mx-auto flex max-w-2xl flex-col flex-col-reverse py-1 sm:flex-row sm:divide-x sm:py-4">
|
||||
<div className="my-5 mr-4 flex flex-col text-lg font-bold">
|
||||
<ResourceLinkSubForm /> {/** //resource={resource} /> */}
|
||||
</div>
|
||||
@ -247,11 +257,11 @@ const ResourceForm = ({ resource }: { resource?: AuditoryResource }) => {
|
||||
General
|
||||
</h1>
|
||||
<div className="justify-left mx-auto flex max-w-lg flex-col space-y-4 pb-5">
|
||||
<ResourceSummarySubForm resource={resource} />
|
||||
<ResourceDescriptionSubForm resource={resource} />
|
||||
<ResourceSummarySubForm register={register} resource={resource} />
|
||||
<ResourceDescriptionSubForm register={register} resource={resource} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -8,11 +8,17 @@ import {
|
||||
import Footer from "~/components/Footer";
|
||||
import Header from "~/components/Header";
|
||||
import { AdminBarLayout } from "~/components/admin/ControlBar";
|
||||
import { AdminActionLink } from "~/components/admin/common";
|
||||
import { AdminActionButton, AdminActionLink } from "~/components/admin/common";
|
||||
import { appRouter } from "~/server/api/root";
|
||||
import { prisma } from "~/server/db";
|
||||
import Image from "next/image";
|
||||
import { ResourceForm } from "~/components/admin/resources/form";
|
||||
import {
|
||||
ResourceForm,
|
||||
type ResourceUpdateInput,
|
||||
} from "~/components/admin/resources/form";
|
||||
import { useState } from "react";
|
||||
import { useForm, type SubmitHandler } from "react-hook-form";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<{
|
||||
resource: AuditoryResource;
|
||||
@ -40,13 +46,29 @@ const EditResourcePage = (
|
||||
props: InferGetServerSidePropsType<typeof getServerSideProps>
|
||||
) => {
|
||||
const { resource } = props;
|
||||
const [serverError, setServerError] = useState<string | undefined>(undefined);
|
||||
const { register, getValues } = useForm<ResourceUpdateInput>({
|
||||
defaultValues: resource as ResourceUpdateInput,
|
||||
});
|
||||
|
||||
const { mutate } = api.auditoryResource.update.useMutation({
|
||||
onSuccess: async (data) => {
|
||||
// todo:
|
||||
},
|
||||
onError: (error) => setServerError(error.message),
|
||||
});
|
||||
|
||||
const onSubmit: SubmitHandler<ResourceUpdateInput> = (data) => {
|
||||
console.log("mutating");
|
||||
mutate(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<AdminBarLayout
|
||||
actions={[
|
||||
<AdminActionLink
|
||||
<AdminActionButton
|
||||
key="save"
|
||||
symbol={
|
||||
<span className="flex">
|
||||
@ -67,7 +89,9 @@ const EditResourcePage = (
|
||||
</span>
|
||||
}
|
||||
label="Save"
|
||||
href={`/resources/${resource.id}`}
|
||||
onClick={() => {
|
||||
onSubmit(getValues());
|
||||
}}
|
||||
/>,
|
||||
<AdminActionLink
|
||||
key="cancel"
|
||||
@ -78,7 +102,11 @@ const EditResourcePage = (
|
||||
]}
|
||||
>
|
||||
<main className="mb-12">
|
||||
<ResourceForm resource={resource} />
|
||||
<ResourceForm
|
||||
register={register}
|
||||
error={serverError}
|
||||
resource={resource as ResourceUpdateInput}
|
||||
/>
|
||||
</main>
|
||||
</AdminBarLayout>
|
||||
<Footer />
|
||||
|
@ -3,10 +3,15 @@ import {
|
||||
Skill,
|
||||
Platform,
|
||||
type AuditoryResource,
|
||||
PaymentType,
|
||||
} from "@prisma/client";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
|
||||
import {
|
||||
createTRPCRouter,
|
||||
protectedProcedure,
|
||||
publicProcedure,
|
||||
} from "~/server/api/trpc";
|
||||
|
||||
export const auditoryResourceRouter = createTRPCRouter({
|
||||
byId: publicProcedure
|
||||
@ -25,6 +30,43 @@ export const auditoryResourceRouter = createTRPCRouter({
|
||||
return ctx.prisma.auditoryResource.findMany();
|
||||
}),
|
||||
|
||||
update: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
icon: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
manufacturer: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
required: z.boolean(),
|
||||
notice: z.string().optional().nullable(),
|
||||
})
|
||||
.optional(),
|
||||
ages: z
|
||||
.object({ min: z.number().int(), max: z.number().int() })
|
||||
.optional(),
|
||||
skills: z.array(z.nativeEnum(Skill)).optional(),
|
||||
skill_levels: z.array(z.nativeEnum(SkillLevel)).optional(),
|
||||
payment_options: z.array(z.nativeEnum(PaymentType)).optional(),
|
||||
platform_links: z
|
||||
.array(
|
||||
z.object({ platform: z.nativeEnum(Platform), link: z.string() })
|
||||
)
|
||||
.optional(),
|
||||
})
|
||||
)
|
||||
.mutation(({ input, ctx }) => {
|
||||
console.log(input);
|
||||
// return await ctx.prisma.auditoryResource.update({
|
||||
// where: {
|
||||
// id: input.id,
|
||||
// },
|
||||
// data: { ...input },
|
||||
// });
|
||||
}),
|
||||
|
||||
search: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
|
Loading…
x
Reference in New Issue
Block a user