Change mode toggle. Add type edit
This commit is contained in:
		
							
								
								
									
										46
									
								
								src/actions/items/commissions/types/updateType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/actions/items/commissions/types/updateType.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
			
		||||
"use server"
 | 
			
		||||
 | 
			
		||||
import prisma from "@/lib/prisma"
 | 
			
		||||
import { commissionTypeSchema } from "@/schemas/commissionType"
 | 
			
		||||
import * as z from "zod/v4"
 | 
			
		||||
 | 
			
		||||
export async function updateCommissionType(
 | 
			
		||||
  id: string,
 | 
			
		||||
  rawData: z.infer<typeof commissionTypeSchema>
 | 
			
		||||
) {
 | 
			
		||||
  const data = commissionTypeSchema.parse(rawData)
 | 
			
		||||
 | 
			
		||||
  const updated = await prisma.commissionType.update({
 | 
			
		||||
    where: { id },
 | 
			
		||||
    data: {
 | 
			
		||||
      name: data.name,
 | 
			
		||||
      description: data.description,
 | 
			
		||||
      options: {
 | 
			
		||||
        deleteMany: {},
 | 
			
		||||
        create: data.options?.map((opt, index) => ({
 | 
			
		||||
          option: { connect: { id: opt.optionId } },
 | 
			
		||||
          price: opt.price ?? null,
 | 
			
		||||
          pricePercent: opt.pricePercent ?? null,
 | 
			
		||||
          priceRange: opt.priceRange ?? null,
 | 
			
		||||
          sortIndex: index,
 | 
			
		||||
        })),
 | 
			
		||||
      },
 | 
			
		||||
      extras: {
 | 
			
		||||
        deleteMany: {},
 | 
			
		||||
        create: data.extras?.map((ext, index) => ({
 | 
			
		||||
          extra: { connect: { id: ext.extraId } },
 | 
			
		||||
          price: ext.price ?? null,
 | 
			
		||||
          pricePercent: ext.pricePercent ?? null,
 | 
			
		||||
          priceRange: ext.priceRange ?? null,
 | 
			
		||||
          sortIndex: index,
 | 
			
		||||
        })),
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    include: {
 | 
			
		||||
      options: true,
 | 
			
		||||
      extras: true,
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return updated
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								src/app/items/commissions/types/[id]/edit/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/app/items/commissions/types/[id]/edit/page.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
import EditTypeForm from "@/components/items/commissions/types/EditTypeForm";
 | 
			
		||||
import prisma from "@/lib/prisma";
 | 
			
		||||
 | 
			
		||||
export default async function CommissionTypesEditPage({ params }: { params: { id: string } }) {
 | 
			
		||||
  const { id } = await params;
 | 
			
		||||
  const commissionType = await prisma.commissionType.findUnique({
 | 
			
		||||
    where: {
 | 
			
		||||
      id,
 | 
			
		||||
    },
 | 
			
		||||
    include: {
 | 
			
		||||
      options: { include: { option: true }, orderBy: { sortIndex: "asc" } },
 | 
			
		||||
      extras: { include: { extra: true }, orderBy: { sortIndex: "asc" } },
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
  const options = await prisma.commissionOption.findMany({
 | 
			
		||||
    orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
 | 
			
		||||
  });
 | 
			
		||||
  const extras = await prisma.commissionExtra.findMany({
 | 
			
		||||
    orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  if (!commissionType) {
 | 
			
		||||
    return <div>Type not found</div>
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <div className="flex gap-4 justify-between pb-8">
 | 
			
		||||
        <h1 className="text-2xl font-bold mb-4">New Commission Type</h1>
 | 
			
		||||
      </div>
 | 
			
		||||
      <EditTypeForm type={commissionType} allOptions={options} allExtras={extras} />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -8,7 +8,7 @@ import { useEffect, useState } from "react"
 | 
			
		||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
 | 
			
		||||
 | 
			
		||||
const modes = ["light", "dark"] as const
 | 
			
		||||
const accents = ["zinc", "red", "rose", "orange", "green", "blue", "yellow", "violet", "default"] as const
 | 
			
		||||
const accents = ["zinc", "red", "rose", "orange", "green", "blue", "yellow", "violet"] as const
 | 
			
		||||
 | 
			
		||||
const modeIcons = {
 | 
			
		||||
  light: <Sun className="h-4 w-4" />,
 | 
			
		||||
@ -16,7 +16,7 @@ const modeIcons = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function ModeToggle() {
 | 
			
		||||
  const { setTheme, theme, resolvedTheme } = useTheme()
 | 
			
		||||
  const { setTheme, theme } = useTheme()
 | 
			
		||||
  const [mode, setMode] = useState("dark")
 | 
			
		||||
  const [accent, setAccent] = useState("violet")
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,17 @@ export default function ModeToggle() {
 | 
			
		||||
    setTheme(fullTheme)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const accentColorMap: Record<string, string> = {
 | 
			
		||||
    zinc: "text-zinc-600",
 | 
			
		||||
    red: "text-red-600",
 | 
			
		||||
    rose: "text-rose-600",
 | 
			
		||||
    orange: "text-orange-600",
 | 
			
		||||
    green: "text-green-600",
 | 
			
		||||
    blue: "text-blue-600",
 | 
			
		||||
    yellow: "text-yellow-600",
 | 
			
		||||
    violet: "text-violet-600",
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex gap-4 items-center">
 | 
			
		||||
      <Select
 | 
			
		||||
@ -71,7 +82,7 @@ export default function ModeToggle() {
 | 
			
		||||
        </SelectTrigger>
 | 
			
		||||
        <SelectContent>
 | 
			
		||||
          {accents.map((a) => (
 | 
			
		||||
            <SelectItem key={a} value={a} className={cn(`text-${a}-600`)}>
 | 
			
		||||
            <SelectItem key={a} value={a} className={cn(accentColorMap[a])}>
 | 
			
		||||
              {a.charAt(0).toUpperCase() + a.slice(1)}
 | 
			
		||||
            </SelectItem>
 | 
			
		||||
          ))}
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,11 @@ export default function TopNav() {
 | 
			
		||||
            <Link href="/">Home</Link>
 | 
			
		||||
          </NavigationMenuLink>
 | 
			
		||||
        </NavigationMenuItem>
 | 
			
		||||
        <NavigationMenuItem>
 | 
			
		||||
          <NavigationMenuLink asChild className={navigationMenuTriggerStyle()}>
 | 
			
		||||
            <Link href="/items/commissions/types">CommissionTypes</Link>
 | 
			
		||||
          </NavigationMenuLink>
 | 
			
		||||
        </NavigationMenuItem>
 | 
			
		||||
      </NavigationMenuList>
 | 
			
		||||
    </NavigationMenu>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										105
									
								
								src/components/items/commissions/types/EditTypeForm.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/components/items/commissions/types/EditTypeForm.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,105 @@
 | 
			
		||||
"use client"
 | 
			
		||||
 | 
			
		||||
import { updateCommissionType } from "@/actions/items/commissions/types/updateType";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { CommissionExtra, CommissionOption, CommissionType, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma";
 | 
			
		||||
import { commissionTypeSchema } from "@/schemas/commissionType";
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
			
		||||
import { useRouter } from "next/navigation";
 | 
			
		||||
import { useForm } from "react-hook-form";
 | 
			
		||||
import { toast } from "sonner";
 | 
			
		||||
import * as z from "zod/v4";
 | 
			
		||||
import { CommissionExtraField } from "./form/CommissionExtraField";
 | 
			
		||||
import { CommissionOptionField } from "./form/CommissionOptionField";
 | 
			
		||||
 | 
			
		||||
type CommissionTypeWithConnections = CommissionType & {
 | 
			
		||||
  options: (CommissionTypeOption & { option: CommissionOption })[]
 | 
			
		||||
  extras: (CommissionTypeExtra & { extra: CommissionExtra })[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  type: CommissionTypeWithConnections
 | 
			
		||||
  allOptions: CommissionOption[],
 | 
			
		||||
  allExtras: CommissionExtra[],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function EditTypeForm({ type, allOptions, allExtras }: Props) {
 | 
			
		||||
  const router = useRouter();
 | 
			
		||||
  const form = useForm<z.infer<typeof commissionTypeSchema>>({
 | 
			
		||||
    resolver: zodResolver(commissionTypeSchema),
 | 
			
		||||
    defaultValues: {
 | 
			
		||||
      name: type.name,
 | 
			
		||||
      description: type.description ?? "",
 | 
			
		||||
      options: type.options.map((o) => ({
 | 
			
		||||
        optionId: o.optionId,
 | 
			
		||||
        price: o.price ?? undefined,
 | 
			
		||||
        pricePercent: o.pricePercent ?? undefined,
 | 
			
		||||
        priceRange: o.priceRange ?? undefined,
 | 
			
		||||
      })),
 | 
			
		||||
      extras: type.extras.map((e) => ({
 | 
			
		||||
        extraId: e.extraId,
 | 
			
		||||
        price: e.price ?? undefined,
 | 
			
		||||
        pricePercent: e.pricePercent ?? undefined,
 | 
			
		||||
        priceRange: e.priceRange ?? undefined,
 | 
			
		||||
      })),
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  async function onSubmit(values: z.infer<typeof commissionTypeSchema>) {
 | 
			
		||||
    try {
 | 
			
		||||
      await updateCommissionType(type.id, values)
 | 
			
		||||
      toast.success("Commission type updated.")
 | 
			
		||||
      router.push("/items/commissions/types")
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      console.error(err)
 | 
			
		||||
      toast("Failed to create commission type.")
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col gap-8">
 | 
			
		||||
      <Form {...form}>
 | 
			
		||||
        <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="name"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel>Name</FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Input {...field} />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
                <FormDescription>The name of the commission type.</FormDescription>
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="description"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel>Description</FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Input {...field} />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
                <FormDescription>Optional description.</FormDescription>
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <CommissionOptionField options={allOptions} />
 | 
			
		||||
          <CommissionExtraField extras={allExtras} />
 | 
			
		||||
 | 
			
		||||
          <div className="flex flex-col gap-4">
 | 
			
		||||
            <Button type="submit">Submit</Button>
 | 
			
		||||
            <Button type="reset" variant="secondary" onClick={() => router.back()}>Cancel</Button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </form>
 | 
			
		||||
      </Form>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
 | 
			
		||||
import { updateCommissionTypeSortOrder } from "@/actions/items/commissions/types/updateCommissionTypeSortOrder";
 | 
			
		||||
import SortableItemCard from "@/components/drag/SortableItemCard";
 | 
			
		||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
 | 
			
		||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
 | 
			
		||||
import { CommissionExtra, CommissionOption, CommissionType, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma";
 | 
			
		||||
import {
 | 
			
		||||
  closestCenter,
 | 
			
		||||
@ -17,6 +17,8 @@ import {
 | 
			
		||||
  rectSortingStrategy,
 | 
			
		||||
  SortableContext
 | 
			
		||||
} from "@dnd-kit/sortable";
 | 
			
		||||
import { PencilIcon } from "lucide-react";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
 | 
			
		||||
type CommissionTypeWithItems = CommissionType & {
 | 
			
		||||
@ -66,6 +68,7 @@ export default function ListTypes({ types }: { types: CommissionTypeWithItems[]
 | 
			
		||||
                <CardHeader>
 | 
			
		||||
                  <CardTitle className="text-xl truncate">{type.name}</CardTitle>
 | 
			
		||||
                  <CardDescription>{type.description}</CardDescription>
 | 
			
		||||
 | 
			
		||||
                </CardHeader>
 | 
			
		||||
                <CardContent className="flex flex-col justify-start gap-4">
 | 
			
		||||
                  <div>
 | 
			
		||||
@ -103,6 +106,15 @@ export default function ListTypes({ types }: { types: CommissionTypeWithItems[]
 | 
			
		||||
                    </ul>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </CardContent>
 | 
			
		||||
                <CardFooter className="flex items-center justify-between">
 | 
			
		||||
                  <Link
 | 
			
		||||
                    href={`/items/commissions/types/${type.id}/edit`}
 | 
			
		||||
                    className="text-sm text-primary hover:underline flex items-center gap-1"
 | 
			
		||||
                  >
 | 
			
		||||
                    <PencilIcon className="h-4 w-4" />
 | 
			
		||||
                    Edit
 | 
			
		||||
                  </Link>
 | 
			
		||||
                </CardFooter>
 | 
			
		||||
              </Card>
 | 
			
		||||
            </SortableItemCard >
 | 
			
		||||
          ))}
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ import {
 | 
			
		||||
  SortableContext,
 | 
			
		||||
  verticalListSortingStrategy
 | 
			
		||||
} from "@dnd-kit/sortable"
 | 
			
		||||
import { useState } from "react"
 | 
			
		||||
import { useEffect, useState } from "react"
 | 
			
		||||
import { useFieldArray, useFormContext } from "react-hook-form"
 | 
			
		||||
import { ComboboxCreateable } from "./ComboboxCreateable"
 | 
			
		||||
 | 
			
		||||
@ -33,12 +33,17 @@ type Props = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CommissionExtraField({ extras: initialExtras }: Props) {
 | 
			
		||||
  const [mounted, setMounted] = useState(false)
 | 
			
		||||
  const { control } = useFormContext()
 | 
			
		||||
  const { fields, append, remove, move } = useFieldArray({
 | 
			
		||||
    control,
 | 
			
		||||
    name: "extras",
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setMounted(true)
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  const [extras, setExtras] = useState(initialExtras)
 | 
			
		||||
 | 
			
		||||
  const sensors = useSensors(useSensor(PointerSensor))
 | 
			
		||||
@ -58,6 +63,8 @@ export function CommissionExtraField({ extras: initialExtras }: Props) {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!mounted) return null
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="space-y-4">
 | 
			
		||||
      <h3 className="text-lg font-semibold">Extras</h3>
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ import {
 | 
			
		||||
  SortableContext,
 | 
			
		||||
  verticalListSortingStrategy
 | 
			
		||||
} from "@dnd-kit/sortable"
 | 
			
		||||
import { useState } from "react"
 | 
			
		||||
import { useEffect, useState } from "react"
 | 
			
		||||
import { useFieldArray, useFormContext } from "react-hook-form"
 | 
			
		||||
import { ComboboxCreateable } from "./ComboboxCreateable"
 | 
			
		||||
 | 
			
		||||
@ -33,12 +33,17 @@ type Props = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CommissionOptionField({ options: initialOptions }: Props) {
 | 
			
		||||
  const [mounted, setMounted] = useState(false)
 | 
			
		||||
  const { control } = useFormContext()
 | 
			
		||||
  const { fields, append, remove, move } = useFieldArray({
 | 
			
		||||
    control,
 | 
			
		||||
    name: "options",
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setMounted(true)
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  const [options, setOptions] = useState(initialOptions)
 | 
			
		||||
 | 
			
		||||
  const sensors = useSensors(useSensor(PointerSensor))
 | 
			
		||||
@ -58,6 +63,8 @@ export function CommissionOptionField({ options: initialOptions }: Props) {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!mounted) return null
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="space-y-4">
 | 
			
		||||
      <h3 className="text-lg font-semibold">Options</h3>
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user