diff --git a/bun.lock b/bun.lock index 884ba94..131739b 100644 --- a/bun.lock +++ b/bun.lock @@ -6,12 +6,15 @@ "dependencies": { "@aws-sdk/client-s3": "^3.954.0", "@aws-sdk/s3-request-presigner": "^3.954.0", + "@hookform/resolvers": "^5.2.2", "@prisma/adapter-pg": "^7.2.0", "@prisma/client": "^7.2.0", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-separator": "^1.1.8", @@ -26,8 +29,10 @@ "pg": "^8.16.3", "react": "19.2.1", "react-dom": "19.2.1", + "react-hook-form": "^7.69.0", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", + "zod": "^4.2.1", }, "devDependencies": { "@biomejs/biome": "2.2.0", @@ -174,6 +179,8 @@ "@hono/node-server": ["@hono/node-server@1.19.6", "", { "peerDependencies": { "hono": "^4" } }, "sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw=="], + "@hookform/resolvers": ["@hookform/resolvers@5.2.2", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA=="], + "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="], "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], @@ -312,6 +319,8 @@ "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], + "@radix-ui/react-label": ["@radix-ui/react-label@2.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A=="], + "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w=="], @@ -458,6 +467,8 @@ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], + "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], "@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="], @@ -682,6 +693,8 @@ "react-dom": ["react-dom@19.2.1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="], + "react-hook-form": ["react-hook-form@7.69.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-yt6ZGME9f4F6WHwevrvpAjh42HMvocuSnSIHUGycBqXIJdhqGSPQzTpGF+1NLREk/58IdPxEMfPcFCjlMhclGw=="], + "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], @@ -756,6 +769,8 @@ "zeptomatch": ["zeptomatch@2.0.2", "", { "dependencies": { "grammex": "^3.1.10" } }, "sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g=="], + "zod": ["zod@4.2.1", "", {}, "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw=="], + "@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], @@ -772,6 +787,8 @@ "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-label/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], + "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], diff --git a/package.json b/package.json index 3ca9b1a..6c29eb8 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,15 @@ "dependencies": { "@aws-sdk/client-s3": "^3.954.0", "@aws-sdk/s3-request-presigner": "^3.954.0", + "@hookform/resolvers": "^5.2.2", "@prisma/adapter-pg": "^7.2.0", "@prisma/client": "^7.2.0", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-separator": "^1.1.8", @@ -32,8 +35,10 @@ "pg": "^8.16.3", "react": "19.2.1", "react-dom": "19.2.1", + "react-hook-form": "^7.69.0", "sonner": "^2.0.7", - "tailwind-merge": "^3.4.0" + "tailwind-merge": "^3.4.0", + "zod": "^4.2.1" }, "devDependencies": { "@biomejs/biome": "2.2.0", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 68c4edb..bac4c71 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -222,3 +222,127 @@ model FileVariant { @@unique([artworkId, type]) } + +model Commission { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) +} + +model CommissionType { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) + + name String + + description String? + + options CommissionTypeOption[] + extras CommissionTypeExtra[] + customInputs CommissionTypeCustomInput[] +} + +model CommissionOption { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) + + name String + + description String? + + types CommissionTypeOption[] +} + +model CommissionTypeOption { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) + + typeId String + optionId String + + priceRange String? + pricePercent Float? + price Float? + + type CommissionType @relation(fields: [typeId], references: [id]) + option CommissionOption @relation(fields: [optionId], references: [id]) + + @@unique([typeId, optionId]) +} + +model CommissionExtra { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) + + name String + + description String? + + types CommissionTypeExtra[] +} + +model CommissionTypeExtra { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) + + typeId String + extraId String + + priceRange String? + pricePercent Float? + price Float? + + type CommissionType @relation(fields: [typeId], references: [id]) + extra CommissionExtra @relation(fields: [extraId], references: [id]) + + @@unique([typeId, extraId]) +} + +model CommissionCustomInput { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) + + name String @unique + fieldId String + + types CommissionTypeCustomInput[] +} + +model CommissionTypeCustomInput { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) + + typeId String + customInputId String + + inputType String + label String + required Boolean @default(false) + + type CommissionType @relation(fields: [typeId], references: [id]) + customInput CommissionCustomInput @relation(fields: [customInputId], references: [id]) + + @@unique([typeId, customInputId]) +} + +model CommissionRequest { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) +} diff --git a/src/app/(normal)/commissions/page.tsx b/src/app/(normal)/commissions/page.tsx new file mode 100644 index 0000000..fb98cee --- /dev/null +++ b/src/app/(normal)/commissions/page.tsx @@ -0,0 +1,28 @@ +import { CommissionCard } from "@/components/commissions/CommissionCard"; +import { CommissionOrderForm } from "@/components/commissions/CommissionOrderForm"; +import { prisma } from "@/lib/prisma"; + +export default async function CommissionsPage() { + const commissions = await prisma.commissionType.findMany({ + include: { + options: { include: { option: true }, orderBy: { sortIndex: "asc" } }, + extras: { include: { extra: true }, orderBy: { sortIndex: "asc" } }, + customInputs: { include: { customInput: true }, orderBy: { sortIndex: "asc" } }, + }, + orderBy: [{ sortIndex: "asc" }, { name: "asc" }], + }) + + return ( +
+

Commission Pricing

+
+ {commissions.map((commission) => ( + + ))} +
+
+

Request a Commission

+ +
+ ); +} \ No newline at end of file diff --git a/src/components/commissions/CommissionCard.tsx b/src/components/commissions/CommissionCard.tsx new file mode 100644 index 0000000..a19c474 --- /dev/null +++ b/src/components/commissions/CommissionCard.tsx @@ -0,0 +1,97 @@ +"use client" + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { CommissionExtra, CommissionOption, CommissionType, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma/client" + +type CommissionTypeWithItems = CommissionType & { + options: (CommissionTypeOption & { + option: CommissionOption | null + })[] + extras: (CommissionTypeExtra & { + extra: CommissionExtra | null + })[] +} + +export function CommissionCard({ commission }: { commission: CommissionTypeWithItems }) { + // const [open, setOpen] = useState(false) + + return ( +
+ + + {commission.name} +

{commission.description}

+
+ + + {/* {examples && examples.length > 0 && ( + + + {open ? "Hide Examples" : "See Examples"} + + +
+
+ {examples.map((src, idx) => ( + {`${type.name} + ))} +
+
+
+
+ )} */} +
+

Options

+
    + {commission.options.map((option) => ( +
  • + {option.option?.name}:{" "} + {option.price + ? `${option.price}€` + : option.pricePercent + ? `+${option.pricePercent}%` + : option.priceRange + ? `${option.priceRange}€` + : "Included"} +
  • + ))} +
+
+ +
+

Extras

+
    + {commission.extras.map((extra) => ( +
  • + {extra.extra?.name}:{" "} + {extra.price + ? `${extra.price}€` + : extra.pricePercent + ? `+${extra.pricePercent}%` + : extra.priceRange + ? `${extra.priceRange}€` + : "Included"} +
  • + ))} +
+
+ + {/*
+ {commission.extras.map((extra) => ( + + {extra.extra?.name} + + ))} +
*/} +
+
+
+ ) +} diff --git a/src/components/commissions/CommissionOrderForm.tsx b/src/components/commissions/CommissionOrderForm.tsx new file mode 100644 index 0000000..d45418d --- /dev/null +++ b/src/components/commissions/CommissionOrderForm.tsx @@ -0,0 +1,304 @@ +"use client" + +import { Button } from "@/components/ui/button" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { CommissionCustomInput, CommissionExtra, CommissionOption, CommissionType, CommissionTypeCustomInput, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma/client" +import { commissionOrderSchema } from "@/schemas/commissionOrder" +import { calculatePriceRange } from "@/utils/calculatePrice" +import { zodResolver } from "@hookform/resolvers/zod" +import Link from "next/link" +import { useMemo, useState } from "react" +import { useForm, useWatch } from "react-hook-form" +import * as z from "zod/v4" +import { FileDropzone } from "./FileDropzone" + +type CommissionTypeWithRelations = CommissionType & { + options: (CommissionTypeOption & { option: CommissionOption })[] + extras: (CommissionTypeExtra & { extra: CommissionExtra })[] + customInputs: (CommissionTypeCustomInput & { customInput: CommissionCustomInput })[] +} + +type Props = { + types: CommissionTypeWithRelations[] +} + +export function CommissionOrderForm({ types }: Props) { + const form = useForm>({ + resolver: zodResolver(commissionOrderSchema), + defaultValues: { + typeId: "", + optionId: "", + extraIds: [], + customerName: "", + customerEmail: "", + message: "", + }, + }) + + const [files, setFiles] = useState([]) + + const typeId = useWatch({ control: form.control, name: "typeId" }) + const optionId = useWatch({ control: form.control, name: "optionId" }) + const extraIds = useWatch({ control: form.control, name: "extraIds" }) + + const selectedType = useMemo( + () => types.find((t) => t.id === typeId), + [types, typeId] + ) + + const selectedOption = useMemo( + () => selectedType?.options.find((o) => o.optionId === optionId), + [selectedType, optionId] + ) + + const selectedExtras = useMemo( + () => selectedType?.extras.filter((e) => extraIds?.includes(e.extraId)) ?? [], + [selectedType, extraIds] + ) + + const [minPrice, maxPrice] = useMemo(() => { + return calculatePriceRange(selectedOption, selectedExtras) + }, [selectedOption, selectedExtras]) + + async function onSubmit(values: z.infer) { + const { customFields, ...rest } = values + console.log("Submit:", { ...rest, customFields, files }) + } + + return ( +
+ + ( + + Choose a commission type + +
+ {types.map((type) => ( + + ))} +
+
+ +
+ )} + /> + + {selectedType && ( + <> + ( + + Base Option + +
+ {selectedType.options.map((opt) => ( + + ))} +
+
+ +
+ )} + /> + + ( + + Extras + +
+ {selectedType?.extras.map((ext) => ( + + ))} +
+
+ +
+ )} + /> + + )} + + + +
+ ( + + Your Name + + + + + + )} + /> + ( + + Email + + + + + + )} + /> +
+ + ( + + Project Details + +