Doing stuff
This commit is contained in:
		
							
								
								
									
										19
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
# Base image
 | 
			
		||||
FROM node:20
 | 
			
		||||
 | 
			
		||||
# Set working directory
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
# Install dependencies
 | 
			
		||||
COPY package.json package-lock.json ./
 | 
			
		||||
RUN npm install
 | 
			
		||||
 | 
			
		||||
# Copy the rest of the code
 | 
			
		||||
COPY . .
 | 
			
		||||
RUN npx prisma generate
 | 
			
		||||
 | 
			
		||||
# Expose the dev port
 | 
			
		||||
EXPOSE 3000
 | 
			
		||||
 | 
			
		||||
# Run dev server
 | 
			
		||||
CMD ["npm", "run", "dev"]
 | 
			
		||||
							
								
								
									
										41
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										41
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -9,6 +9,7 @@
 | 
			
		||||
      "version": "0.1.0",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@hookform/resolvers": "^5.1.1",
 | 
			
		||||
        "@prisma/client": "^6.11.1",
 | 
			
		||||
        "@radix-ui/react-checkbox": "^1.3.2",
 | 
			
		||||
        "@radix-ui/react-collapsible": "^1.1.11",
 | 
			
		||||
        "@radix-ui/react-dropdown-menu": "^2.1.15",
 | 
			
		||||
@ -1041,11 +1042,33 @@
 | 
			
		||||
        "url": "https://opencollective.com/pkgr"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@prisma/client": {
 | 
			
		||||
      "version": "6.11.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.11.1.tgz",
 | 
			
		||||
      "integrity": "sha512-5CLFh8QP6KxRm83pJ84jaVCeSVPQr8k0L2SEtOJHwdkS57/VQDcI/wQpGmdyOZi+D9gdNabdo8tj1Uk+w+upsQ==",
 | 
			
		||||
      "hasInstallScript": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=18.18"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "prisma": "*",
 | 
			
		||||
        "typescript": ">=5.1.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "prisma": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "typescript": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@prisma/config": {
 | 
			
		||||
      "version": "6.11.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.11.1.tgz",
 | 
			
		||||
      "integrity": "sha512-z6rCTQN741wxDq82cpdzx2uVykpnQIXalLhrWQSR0jlBVOxCIkz3HZnd8ern3uYTcWKfB3IpVAF7K2FU8t/8AQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "jiti": "2.4.2"
 | 
			
		||||
@ -1055,14 +1078,14 @@
 | 
			
		||||
      "version": "6.11.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.11.1.tgz",
 | 
			
		||||
      "integrity": "sha512-lWRb/YSWu8l4Yum1UXfGLtqFzZkVS2ygkWYpgkbgMHn9XJlMITIgeMvJyX5GepChzhmxuSuiq/MY/kGFweOpGw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "license": "Apache-2.0"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@prisma/engines": {
 | 
			
		||||
      "version": "6.11.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.11.1.tgz",
 | 
			
		||||
      "integrity": "sha512-6eKEcV6V8W2eZAUwX2xTktxqPM4vnx3sxz3SDtpZwjHKpC6lhOtc4vtAtFUuf5+eEqBk+dbJ9Dcaj6uQU+FNNg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "hasInstallScript": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
@ -1076,14 +1099,14 @@
 | 
			
		||||
      "version": "6.11.1-1.f40f79ec31188888a2e33acda0ecc8fd10a853a9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.11.1-1.f40f79ec31188888a2e33acda0ecc8fd10a853a9.tgz",
 | 
			
		||||
      "integrity": "sha512-swFJTOOg4tHyOM1zB/pHb3MeH0i6t7jFKn5l+ZsB23d9AQACuIRo9MouvuKGvnDogzkcjbWnXi/NvOZ0+n5Jfw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "license": "Apache-2.0"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@prisma/fetch-engine": {
 | 
			
		||||
      "version": "6.11.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.11.1.tgz",
 | 
			
		||||
      "integrity": "sha512-NBYzmkXTkj9+LxNPRSndaAeALOL1Gr3tjvgRYNqruIPlZ6/ixLeuE/5boYOewant58tnaYFZ5Ne0jFBPfGXHpQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@prisma/debug": "6.11.1",
 | 
			
		||||
@ -1095,7 +1118,7 @@
 | 
			
		||||
      "version": "6.11.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.11.1.tgz",
 | 
			
		||||
      "integrity": "sha512-b2Z8oV2gwvdCkFemBTFd0x4lsL4O2jLSx8lB7D+XqoFALOQZPa7eAPE1NU0Mj1V8gPHRxIsHnyUNtw2i92psUw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@prisma/debug": "6.11.1"
 | 
			
		||||
@ -5124,7 +5147,7 @@
 | 
			
		||||
      "version": "2.4.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
 | 
			
		||||
      "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "jiti": "lib/jiti-cli.mjs"
 | 
			
		||||
@ -6107,7 +6130,7 @@
 | 
			
		||||
      "version": "6.11.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.11.1.tgz",
 | 
			
		||||
      "integrity": "sha512-VzJToRlV0s9Vu2bfqHiRJw73hZNCG/AyJeX+kopbu4GATTjTUdEWUteO3p4BLYoHpMS4o8pD3v6tF44BHNZI1w==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "hasInstallScript": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
@ -7168,7 +7191,7 @@
 | 
			
		||||
      "version": "5.8.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
 | 
			
		||||
      "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "tsc": "bin/tsc",
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@hookform/resolvers": "^5.1.1",
 | 
			
		||||
    "@prisma/client": "^6.11.1",
 | 
			
		||||
    "@radix-ui/react-checkbox": "^1.3.2",
 | 
			
		||||
    "@radix-ui/react-collapsible": "^1.1.11",
 | 
			
		||||
    "@radix-ui/react-dropdown-menu": "^2.1.15",
 | 
			
		||||
 | 
			
		||||
@ -13,3 +13,162 @@ datasource db {
 | 
			
		||||
  provider = "postgresql"
 | 
			
		||||
  url      = env("DATABASE_URL")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model CommissionOption {
 | 
			
		||||
  id        String   @id @default(cuid())
 | 
			
		||||
  createdAt DateTime @default(now())
 | 
			
		||||
  updatedAt DateTime @updatedAt
 | 
			
		||||
  sortIndex Int      @default(0)
 | 
			
		||||
 | 
			
		||||
  name String @unique
 | 
			
		||||
 | 
			
		||||
  description String?
 | 
			
		||||
 | 
			
		||||
  types CommissionTypeOption[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model CommissionExtra {
 | 
			
		||||
  id        String   @id @default(cuid())
 | 
			
		||||
  createdAt DateTime @default(now())
 | 
			
		||||
  updatedAt DateTime @updatedAt
 | 
			
		||||
  sortIndex Int      @default(0)
 | 
			
		||||
 | 
			
		||||
  name String @unique
 | 
			
		||||
 | 
			
		||||
  description String?
 | 
			
		||||
 | 
			
		||||
  types CommissionTypeExtra[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 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 User {
 | 
			
		||||
//   id        String   @id @default(cuid())
 | 
			
		||||
//   email     String   @unique
 | 
			
		||||
//   name      String?
 | 
			
		||||
//   role      Role     @default(ADMIN)
 | 
			
		||||
//   createdAt DateTime @default(now())
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// enum Role {
 | 
			
		||||
//   ADMIN
 | 
			
		||||
//   ARTIST
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// model CommissionType {
 | 
			
		||||
//   id                String              @id @default(cuid())
 | 
			
		||||
//   title             String
 | 
			
		||||
//   description       String?
 | 
			
		||||
//   basePrice         Float
 | 
			
		||||
//   deliveryEst       String? // e.g. "2 weeks"
 | 
			
		||||
//   tags              String[] // e.g. shaded, sketch, full-body
 | 
			
		||||
//   active            Boolean             @default(true)
 | 
			
		||||
//   createdAt         DateTime            @default(now())
 | 
			
		||||
//   CommissionRequest CommissionRequest[]
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// model CommissionRequest {
 | 
			
		||||
//   id        String        @id @default(cuid())
 | 
			
		||||
//   name      String
 | 
			
		||||
//   email     String
 | 
			
		||||
//   message   String
 | 
			
		||||
//   typeId    String
 | 
			
		||||
//   status    RequestStatus @default(PENDING)
 | 
			
		||||
//   createdAt DateTime      @default(now())
 | 
			
		||||
 | 
			
		||||
//   type CommissionType @relation(fields: [typeId], references: [id])
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// enum RequestStatus {
 | 
			
		||||
//   PENDING
 | 
			
		||||
//   ACCEPTED
 | 
			
		||||
//   IN_PROGRESS
 | 
			
		||||
//   DONE
 | 
			
		||||
//   REJECTED
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// model Artwork {
 | 
			
		||||
//   id          String   @id @default(cuid())
 | 
			
		||||
//   title       String
 | 
			
		||||
//   imageUrl    String
 | 
			
		||||
//   description String?
 | 
			
		||||
//   tags        String[]
 | 
			
		||||
//   formats     String[]
 | 
			
		||||
//   isPublic    Boolean  @default(true)
 | 
			
		||||
//   groupId     String?
 | 
			
		||||
//   createdAt   DateTime @default(now())
 | 
			
		||||
 | 
			
		||||
//   group PresentationGroup? @relation(fields: [groupId], references: [id])
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// model PresentationGroup {
 | 
			
		||||
//   id          String    @id @default(cuid())
 | 
			
		||||
//   name        String
 | 
			
		||||
//   description String?
 | 
			
		||||
//   createdAt   DateTime  @default(now())
 | 
			
		||||
//   Artwork     Artwork[]
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// model Preferences {
 | 
			
		||||
//   id               String  @id @default(cuid())
 | 
			
		||||
//   commissionOpen   Boolean @default(true)
 | 
			
		||||
//   defaultDelivery  String? // e.g. "7 days"
 | 
			
		||||
//   autoReplyMessage String?
 | 
			
		||||
//   notifyByEmail    Boolean @default(true)
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// model TOS {
 | 
			
		||||
//   id        String   @id @default(cuid())
 | 
			
		||||
//   content   String // Markdown or rich text
 | 
			
		||||
//   createdAt DateTime @default(now())
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
@ -1,20 +1,27 @@
 | 
			
		||||
import { CommissionCard } from "@/components/commissions/CommissionCard";
 | 
			
		||||
import { generateMockCommissions } from "@/utils/generateMockCommissions";
 | 
			
		||||
import { CommissionOrderForm } from "@/components/commissions/CommissionOrderForm";
 | 
			
		||||
import prisma from "@/lib/prisma";
 | 
			
		||||
 | 
			
		||||
export default function CommissionsPage() {
 | 
			
		||||
  const commissions = generateMockCommissions()
 | 
			
		||||
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" } },
 | 
			
		||||
    },
 | 
			
		||||
    orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="container py-10 space-y-10">
 | 
			
		||||
      <h1 className="text-3xl font-bold">Commission Pricing</h1>
 | 
			
		||||
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 items-start">
 | 
			
		||||
        {commissions.map((data) => (
 | 
			
		||||
          <CommissionCard key={data.type.key} {...data} />
 | 
			
		||||
        {commissions.map((commission) => (
 | 
			
		||||
          <CommissionCard key={commission.id} commission={commission} />
 | 
			
		||||
        ))}
 | 
			
		||||
      </div>
 | 
			
		||||
      <hr />
 | 
			
		||||
      <h2 className="text-2xl font-semibold">Request a Commission</h2>
 | 
			
		||||
      {/* <CommissionForm /> */}
 | 
			
		||||
      <CommissionOrderForm types={commissions} />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -1,25 +1,32 @@
 | 
			
		||||
// components/commissions/CommissionCard.tsx
 | 
			
		||||
"use client"
 | 
			
		||||
 | 
			
		||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
 | 
			
		||||
import { CommissionCardProps } from "@/types/commissions"
 | 
			
		||||
import Image from "next/image"
 | 
			
		||||
import { useState } from "react"
 | 
			
		||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible"
 | 
			
		||||
import { CommissionExtra, CommissionOption, CommissionType, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma"
 | 
			
		||||
// import { useState } from "react"
 | 
			
		||||
import { Badge } from "../ui/badge"
 | 
			
		||||
 | 
			
		||||
export function CommissionCard({ type, options, extras, examples }: CommissionCardProps) {
 | 
			
		||||
  const [open, setOpen] = useState(false)
 | 
			
		||||
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 (
 | 
			
		||||
    <div className="flex flex-col h-full">
 | 
			
		||||
      <Card className="flex flex-col flex-1">
 | 
			
		||||
        <CardHeader>
 | 
			
		||||
          <CardTitle className="text-xl font-bold">{type.name}</CardTitle>
 | 
			
		||||
          <p className="text-muted-foreground text-sm">{type.description}</p>
 | 
			
		||||
          <CardTitle className="text-xl font-bold">{commission.name}</CardTitle>
 | 
			
		||||
          <p className="text-muted-foreground text-sm">{commission.description}</p>
 | 
			
		||||
        </CardHeader>
 | 
			
		||||
 | 
			
		||||
        <CardContent className="flex flex-col justify-start gap-4">
 | 
			
		||||
          {examples && examples.length > 0 && (
 | 
			
		||||
          {/* {examples && examples.length > 0 && (
 | 
			
		||||
            <Collapsible open={open} onOpenChange={setOpen}>
 | 
			
		||||
              <CollapsibleTrigger className="text-sm underline text-muted-foreground">
 | 
			
		||||
                {open ? "Hide Examples" : "See Examples"}
 | 
			
		||||
@ -41,13 +48,20 @@ export function CommissionCard({ type, options, extras, examples }: CommissionCa
 | 
			
		||||
                </div>
 | 
			
		||||
              </CollapsibleContent>
 | 
			
		||||
            </Collapsible>
 | 
			
		||||
          )}
 | 
			
		||||
          )} */}
 | 
			
		||||
          <div>
 | 
			
		||||
            <h4 className="font-semibold">Options</h4>
 | 
			
		||||
            <ul className="pl-4 list-disc">
 | 
			
		||||
              {options.map((opt) => (
 | 
			
		||||
                <li key={opt.key}>
 | 
			
		||||
                  {opt.name}: {opt.price}€
 | 
			
		||||
              {commission.options.map((option) => (
 | 
			
		||||
                <li key={option.id}>
 | 
			
		||||
                  {option.option?.name}:{" "}
 | 
			
		||||
                  {option.price
 | 
			
		||||
                    ? `${option.price}€`
 | 
			
		||||
                    : option.pricePercent
 | 
			
		||||
                      ? `+${option.pricePercent}%`
 | 
			
		||||
                      : option.priceRange
 | 
			
		||||
                        ? `${option.priceRange}€`
 | 
			
		||||
                        : "Included"}
 | 
			
		||||
                </li>
 | 
			
		||||
              ))}
 | 
			
		||||
            </ul>
 | 
			
		||||
@ -56,28 +70,28 @@ export function CommissionCard({ type, options, extras, examples }: CommissionCa
 | 
			
		||||
          <div>
 | 
			
		||||
            <h4 className="font-semibold">Extras</h4>
 | 
			
		||||
            <ul className="pl-4 list-disc">
 | 
			
		||||
              {extras.map((extra) => (
 | 
			
		||||
                <li key={extra.key}>
 | 
			
		||||
                  {extra.name}:{" "}
 | 
			
		||||
                  {extra.price !== undefined
 | 
			
		||||
              {commission.extras.map((extra) => (
 | 
			
		||||
                <li key={extra.id}>
 | 
			
		||||
                  {extra.extra?.name}:{" "}
 | 
			
		||||
                  {extra.price
 | 
			
		||||
                    ? `${extra.price}€`
 | 
			
		||||
                    : extra.pricePercent
 | 
			
		||||
                      ? `+${extra.pricePercent}%`
 | 
			
		||||
                      : extra.priceRange
 | 
			
		||||
                        ? `${extra.priceRange.replace("#", "–")}€`
 | 
			
		||||
                        ? `${extra.priceRange}€`
 | 
			
		||||
                        : "Included"}
 | 
			
		||||
                </li>
 | 
			
		||||
              ))}
 | 
			
		||||
            </ul>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          {/* <div className="flex flex-wrap gap-2">
 | 
			
		||||
            {extras.map((extra) => (
 | 
			
		||||
              <Badge variant="outline" key={extra.key}>
 | 
			
		||||
                {extra.name}
 | 
			
		||||
          <div className="flex flex-wrap gap-2">
 | 
			
		||||
            {commission.extras.map((extra) => (
 | 
			
		||||
              <Badge variant="outline" key={extra.id}>
 | 
			
		||||
                {extra.extra?.name}
 | 
			
		||||
              </Badge>
 | 
			
		||||
            ))}
 | 
			
		||||
          </div> */}
 | 
			
		||||
          </div>
 | 
			
		||||
        </CardContent>
 | 
			
		||||
      </Card>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@ -1,55 +0,0 @@
 | 
			
		||||
// src/app/(frontend)/commissions/CommissionForm.tsx
 | 
			
		||||
"use client"
 | 
			
		||||
 | 
			
		||||
import { Button } from "@/components/ui/button"
 | 
			
		||||
import { Input } from "@/components/ui/input"
 | 
			
		||||
import { Label } from "@/components/ui/label"
 | 
			
		||||
import { Textarea } from "@/components/ui/textarea"
 | 
			
		||||
import { CommissionFormData, commissionFormSchema } from "@/schemas/commission"
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod"
 | 
			
		||||
import { useForm } from "react-hook-form"
 | 
			
		||||
// import { CommissionFormData, commissionFormSchema } from "./schema"
 | 
			
		||||
 | 
			
		||||
export function CommissionForm() {
 | 
			
		||||
  const {
 | 
			
		||||
    register,
 | 
			
		||||
    handleSubmit,
 | 
			
		||||
    formState: { errors },
 | 
			
		||||
  } = useForm<CommissionFormData>({
 | 
			
		||||
    resolver: zodResolver(commissionFormSchema),
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  function onSubmit(data: CommissionFormData) {
 | 
			
		||||
    console.log("Order submitted", data)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4 max-w-md">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label htmlFor="name">Name</Label>
 | 
			
		||||
        <Input id="name" {...register("name")} />
 | 
			
		||||
        {errors.name && <p className="text-sm text-red-500">{errors.name.message}</p>}
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label htmlFor="email">Email</Label>
 | 
			
		||||
        <Input id="email" {...register("email")} />
 | 
			
		||||
        {errors.email && <p className="text-sm text-red-500">{errors.email.message}</p>}
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label htmlFor="type">Commission Type</Label>
 | 
			
		||||
        <Input id="type" {...register("type")} />
 | 
			
		||||
        {errors.type && <p className="text-sm text-red-500">{errors.type.message}</p>}
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label htmlFor="message">Description / Message</Label>
 | 
			
		||||
        <Textarea id="message" {...register("message")} rows={5} />
 | 
			
		||||
        {errors.message && <p className="text-sm text-red-500">{errors.message.message}</p>}
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <Button type="submit">Submit Request</Button>
 | 
			
		||||
    </form>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										238
									
								
								src/components/commissions/CommissionOrderForm.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								src/components/commissions/CommissionOrderForm.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,238 @@
 | 
			
		||||
"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 { CommissionExtra, CommissionOption, CommissionType, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma"
 | 
			
		||||
import { commissionOrderSchema } from "@/schemas/commissionOrder"
 | 
			
		||||
import { calculatePrice } from "@/utils/calculatePrice"
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod"
 | 
			
		||||
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 })[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  types: CommissionTypeWithRelations[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CommissionOrderForm({ types }: Props) {
 | 
			
		||||
  const form = useForm<z.infer<typeof commissionOrderSchema>>({
 | 
			
		||||
    resolver: zodResolver(commissionOrderSchema),
 | 
			
		||||
    defaultValues: {
 | 
			
		||||
      typeId: "",
 | 
			
		||||
      optionId: "",
 | 
			
		||||
      extraIds: [],
 | 
			
		||||
      customerName: "",
 | 
			
		||||
      customerEmail: "",
 | 
			
		||||
      message: "",
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const [files, setFiles] = useState<File[]>([])
 | 
			
		||||
 | 
			
		||||
  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 price = useMemo(() => {
 | 
			
		||||
    if (!selectedOption) return 0
 | 
			
		||||
 | 
			
		||||
    const base = calculatePrice(selectedOption, 0)
 | 
			
		||||
    const extrasSum = selectedExtras.reduce(
 | 
			
		||||
      (sum, ext) => sum + calculatePrice(ext, base),
 | 
			
		||||
      0
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    return base + extrasSum
 | 
			
		||||
  }, [selectedOption, selectedExtras])
 | 
			
		||||
 | 
			
		||||
  async function onSubmit(values: z.infer<typeof commissionOrderSchema>) {
 | 
			
		||||
    console.log("Submit:", { ...values, files })
 | 
			
		||||
    // TODO: send to server
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Form {...form}>
 | 
			
		||||
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
 | 
			
		||||
        <FormField
 | 
			
		||||
          control={form.control}
 | 
			
		||||
          name="typeId"
 | 
			
		||||
          render={({ field }) => (
 | 
			
		||||
            <FormItem>
 | 
			
		||||
              <FormLabel>Choose a commission type</FormLabel>
 | 
			
		||||
              <FormControl>
 | 
			
		||||
                <div className="flex flex-wrap gap-2">
 | 
			
		||||
                  {types.map((type) => (
 | 
			
		||||
                    <Button
 | 
			
		||||
                      key={type.id}
 | 
			
		||||
                      type="button"
 | 
			
		||||
                      variant={field.value === type.id ? "default" : "outline"}
 | 
			
		||||
                      onClick={() => field.onChange(type.id)}
 | 
			
		||||
                    >
 | 
			
		||||
                      {type.name}
 | 
			
		||||
                    </Button>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </div>
 | 
			
		||||
              </FormControl>
 | 
			
		||||
              <FormMessage />
 | 
			
		||||
            </FormItem>
 | 
			
		||||
          )}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        {selectedType && (
 | 
			
		||||
          <>
 | 
			
		||||
            <FormField
 | 
			
		||||
              control={form.control}
 | 
			
		||||
              name="optionId"
 | 
			
		||||
              render={({ field }) => (
 | 
			
		||||
                <FormItem>
 | 
			
		||||
                  <FormLabel>Base Option</FormLabel>
 | 
			
		||||
                  <FormControl>
 | 
			
		||||
                    <div className="space-y-1">
 | 
			
		||||
                      {selectedType.options.map((opt) => (
 | 
			
		||||
                        <label key={opt.id} className="flex items-center gap-2">
 | 
			
		||||
                          <input
 | 
			
		||||
                            type="radio"
 | 
			
		||||
                            checked={field.value === opt.optionId}
 | 
			
		||||
                            value={opt.optionId}
 | 
			
		||||
                            onChange={() => field.onChange(opt.optionId)}
 | 
			
		||||
                          />
 | 
			
		||||
                          {opt.option.name}
 | 
			
		||||
                        </label>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </FormControl>
 | 
			
		||||
                  <FormMessage />
 | 
			
		||||
                </FormItem>
 | 
			
		||||
              )}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            <FormField
 | 
			
		||||
              control={form.control}
 | 
			
		||||
              name="extraIds"
 | 
			
		||||
              render={({ field }) => (
 | 
			
		||||
                <FormItem>
 | 
			
		||||
                  <FormLabel>Extras</FormLabel>
 | 
			
		||||
                  <FormControl>
 | 
			
		||||
                    <div className="space-y-1">
 | 
			
		||||
                      {selectedType?.extras.map((ext) => (
 | 
			
		||||
                        <label key={ext.id} className="flex items-center gap-2">
 | 
			
		||||
                          <input
 | 
			
		||||
                            type="checkbox"
 | 
			
		||||
                            checked={field.value?.includes(ext.extraId) ?? false}
 | 
			
		||||
                            value={ext.extraId}
 | 
			
		||||
                            onChange={(e) => {
 | 
			
		||||
                              const checked = e.target.checked
 | 
			
		||||
                              const newSet = new Set(field.value ?? [])
 | 
			
		||||
                              if (checked) {
 | 
			
		||||
                                newSet.add(ext.extraId)
 | 
			
		||||
                              } else {
 | 
			
		||||
                                newSet.delete(ext.extraId)
 | 
			
		||||
                              }
 | 
			
		||||
                              field.onChange(Array.from(newSet))
 | 
			
		||||
                            }}
 | 
			
		||||
                          />
 | 
			
		||||
                          {ext.extra.name}
 | 
			
		||||
                        </label>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </FormControl>
 | 
			
		||||
                  <FormMessage />
 | 
			
		||||
                </FormItem>
 | 
			
		||||
              )}
 | 
			
		||||
            />
 | 
			
		||||
          </>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="customerName"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel>Your Name</FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Input placeholder="Jane Doe" {...field} />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="customerEmail"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel>Email</FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Input placeholder="jane@example.com" {...field} />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <FormField
 | 
			
		||||
          control={form.control}
 | 
			
		||||
          name="message"
 | 
			
		||||
          render={({ field }) => (
 | 
			
		||||
            <FormItem>
 | 
			
		||||
              <FormLabel>Project Details</FormLabel>
 | 
			
		||||
              <FormControl>
 | 
			
		||||
                <Textarea
 | 
			
		||||
                  placeholder="Describe what you’d like drawn..."
 | 
			
		||||
                  rows={4}
 | 
			
		||||
                  {...field}
 | 
			
		||||
                />
 | 
			
		||||
              </FormControl>
 | 
			
		||||
              <FormMessage />
 | 
			
		||||
            </FormItem>
 | 
			
		||||
          )}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
          <FormLabel>Reference Images</FormLabel>
 | 
			
		||||
          <FileDropzone onFilesSelected={(f: File[]) => setFiles(f)} />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div className="text-xl font-semibold">
 | 
			
		||||
          Estimated Price: €{price.toFixed(2)}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <Button type="submit" disabled={!form.formState.isValid}>
 | 
			
		||||
          Submit Request
 | 
			
		||||
        </Button>
 | 
			
		||||
      </form>
 | 
			
		||||
    </Form>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								src/components/commissions/FileDropzone.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/components/commissions/FileDropzone.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
// components/form/FileDropzone.tsx
 | 
			
		||||
"use client"
 | 
			
		||||
import { useCallback } from "react"
 | 
			
		||||
 | 
			
		||||
export function FileDropzone({
 | 
			
		||||
  onFilesSelected,
 | 
			
		||||
}: {
 | 
			
		||||
  onFilesSelected: (files: File[]) => void
 | 
			
		||||
}) {
 | 
			
		||||
  const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
 | 
			
		||||
    if (e.target.files) {
 | 
			
		||||
      onFilesSelected(Array.from(e.target.files))
 | 
			
		||||
    }
 | 
			
		||||
  }, [onFilesSelected])
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <input
 | 
			
		||||
      type="file"
 | 
			
		||||
      multiple
 | 
			
		||||
      onChange={handleChange}
 | 
			
		||||
      className="border p-2 rounded-md"
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
@ -1,311 +1,311 @@
 | 
			
		||||
 | 
			
		||||
export const commissionTypes = {
 | 
			
		||||
  sketch: {
 | 
			
		||||
    name: 'Sketch',
 | 
			
		||||
    description: 'A quick sketch of a character',
 | 
			
		||||
  },
 | 
			
		||||
  flat: {
 | 
			
		||||
    name: 'Flat Color / Lineart',
 | 
			
		||||
    description: 'Dunno how to describe this',
 | 
			
		||||
  }, 
 | 
			
		||||
  shaded: {
 | 
			
		||||
    name: 'Shaded',
 | 
			
		||||
    description: 'Dunno how to describe this',
 | 
			
		||||
  }, 
 | 
			
		||||
  refsheet: {
 | 
			
		||||
    name: 'Reference Sheet',
 | 
			
		||||
    description: 'A reference sheet with multiple poses',
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// export const commissionTypes = {
 | 
			
		||||
//   sketch: {
 | 
			
		||||
//     name: 'Sketch',
 | 
			
		||||
//     description: 'A quick sketch of a character',
 | 
			
		||||
//   },
 | 
			
		||||
//   flat: {
 | 
			
		||||
//     name: 'Flat Color / Lineart',
 | 
			
		||||
//     description: 'Dunno how to describe this',
 | 
			
		||||
//   }, 
 | 
			
		||||
//   shaded: {
 | 
			
		||||
//     name: 'Shaded',
 | 
			
		||||
//     description: 'Dunno how to describe this',
 | 
			
		||||
//   }, 
 | 
			
		||||
//   refsheet: {
 | 
			
		||||
//     name: 'Reference Sheet',
 | 
			
		||||
//     description: 'A reference sheet with multiple poses',
 | 
			
		||||
//   }
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
export const commissionOptions = {
 | 
			
		||||
  headshot: {
 | 
			
		||||
    name: 'Headshot',
 | 
			
		||||
    basePrice: 10
 | 
			
		||||
  },
 | 
			
		||||
  halfbody: {
 | 
			
		||||
    name: 'Halfbody',
 | 
			
		||||
    basePrice: 15
 | 
			
		||||
  },
 | 
			
		||||
  fullbody: {
 | 
			
		||||
    name: 'Fullbody',
 | 
			
		||||
    basePrice: 20
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// export const commissionOptions = {
 | 
			
		||||
//   headshot: {
 | 
			
		||||
//     name: 'Headshot',
 | 
			
		||||
//     basePrice: 10
 | 
			
		||||
//   },
 | 
			
		||||
//   halfbody: {
 | 
			
		||||
//     name: 'Halfbody',
 | 
			
		||||
//     basePrice: 15
 | 
			
		||||
//   },
 | 
			
		||||
//   fullbody: {
 | 
			
		||||
//     name: 'Fullbody',
 | 
			
		||||
//     basePrice: 20
 | 
			
		||||
//   }
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
export const commissionExtras = {
 | 
			
		||||
  extraChar: {
 | 
			
		||||
    name: 'Extra Character',
 | 
			
		||||
    description: 'Add an extra character to the commission',
 | 
			
		||||
    pricePercent: 75,
 | 
			
		||||
    price: 0,
 | 
			
		||||
    priceRange: ""
 | 
			
		||||
  },
 | 
			
		||||
  basicBackground: {
 | 
			
		||||
    name: 'Basic Background',
 | 
			
		||||
    description: 'Add a basic background to the commission',
 | 
			
		||||
    pricePercent: 0,
 | 
			
		||||
    price: 0,
 | 
			
		||||
    priceRange: ""
 | 
			
		||||
  },
 | 
			
		||||
  abstractBackground: {
 | 
			
		||||
    name: 'Abstract Background',
 | 
			
		||||
    description: 'Add an abstract background to the commission',
 | 
			
		||||
    pricePercent: 0,
 | 
			
		||||
    price: 0,
 | 
			
		||||
    priceRange: ""
 | 
			
		||||
  },
 | 
			
		||||
  gradientBackground: {
 | 
			
		||||
    name: 'Gradient Background',
 | 
			
		||||
    description: 'Add an gradient background to the commission',
 | 
			
		||||
    pricePercent: 0,
 | 
			
		||||
    price: 0,
 | 
			
		||||
    priceRange: ""
 | 
			
		||||
  },
 | 
			
		||||
  customBackground: {
 | 
			
		||||
    name: 'Custom Background',
 | 
			
		||||
    description: 'Add a custom background to the commission',
 | 
			
		||||
    pricePercent: 0,
 | 
			
		||||
    price: 0,
 | 
			
		||||
    priceRange: "10#50"
 | 
			
		||||
  },
 | 
			
		||||
  nsfw: {
 | 
			
		||||
    name: 'NSFW',
 | 
			
		||||
    description: 'Add NSFW content to the commission',
 | 
			
		||||
    pricePercent: 15,
 | 
			
		||||
    price: 0,
 | 
			
		||||
    priceRange: ""
 | 
			
		||||
  },
 | 
			
		||||
  normalComplexity: {
 | 
			
		||||
    name: 'Light Complexity',
 | 
			
		||||
    description: 'Increase complexity of the commission',
 | 
			
		||||
    pricePercent: 15,
 | 
			
		||||
    price: 0,
 | 
			
		||||
    priceRange: ""
 | 
			
		||||
  },
 | 
			
		||||
  highComplexity: {
 | 
			
		||||
    name: 'High Complexity',
 | 
			
		||||
    description: 'Highly increase complexity of the commission',
 | 
			
		||||
    pricePercent: 30,
 | 
			
		||||
    price: 0,
 | 
			
		||||
    priceRange: ""
 | 
			
		||||
  },
 | 
			
		||||
  details: {
 | 
			
		||||
    name: 'Details',
 | 
			
		||||
    description: 'Add details to the commission (tails, eyes, ...)',
 | 
			
		||||
    pricePercent: 0,
 | 
			
		||||
    price: 0,
 | 
			
		||||
    priceRange: "3#5"
 | 
			
		||||
  },
 | 
			
		||||
  shading: {
 | 
			
		||||
    name: 'Shading',
 | 
			
		||||
    description: 'Add shading to the commission',
 | 
			
		||||
    pricePercent: 0,
 | 
			
		||||
    price: 20,
 | 
			
		||||
    priceRange: ""
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// export const commissionExtras = {
 | 
			
		||||
//   extraChar: {
 | 
			
		||||
//     name: 'Extra Character',
 | 
			
		||||
//     description: 'Add an extra character to the commission',
 | 
			
		||||
//     pricePercent: 75,
 | 
			
		||||
//     price: 0,
 | 
			
		||||
//     priceRange: ""
 | 
			
		||||
//   },
 | 
			
		||||
//   basicBackground: {
 | 
			
		||||
//     name: 'Basic Background',
 | 
			
		||||
//     description: 'Add a basic background to the commission',
 | 
			
		||||
//     pricePercent: 0,
 | 
			
		||||
//     price: 0,
 | 
			
		||||
//     priceRange: ""
 | 
			
		||||
//   },
 | 
			
		||||
//   abstractBackground: {
 | 
			
		||||
//     name: 'Abstract Background',
 | 
			
		||||
//     description: 'Add an abstract background to the commission',
 | 
			
		||||
//     pricePercent: 0,
 | 
			
		||||
//     price: 0,
 | 
			
		||||
//     priceRange: ""
 | 
			
		||||
//   },
 | 
			
		||||
//   gradientBackground: {
 | 
			
		||||
//     name: 'Gradient Background',
 | 
			
		||||
//     description: 'Add an gradient background to the commission',
 | 
			
		||||
//     pricePercent: 0,
 | 
			
		||||
//     price: 0,
 | 
			
		||||
//     priceRange: ""
 | 
			
		||||
//   },
 | 
			
		||||
//   customBackground: {
 | 
			
		||||
//     name: 'Custom Background',
 | 
			
		||||
//     description: 'Add a custom background to the commission',
 | 
			
		||||
//     pricePercent: 0,
 | 
			
		||||
//     price: 0,
 | 
			
		||||
//     priceRange: "10#50"
 | 
			
		||||
//   },
 | 
			
		||||
//   nsfw: {
 | 
			
		||||
//     name: 'NSFW',
 | 
			
		||||
//     description: 'Add NSFW content to the commission',
 | 
			
		||||
//     pricePercent: 15,
 | 
			
		||||
//     price: 0,
 | 
			
		||||
//     priceRange: ""
 | 
			
		||||
//   },
 | 
			
		||||
//   normalComplexity: {
 | 
			
		||||
//     name: 'Light Complexity',
 | 
			
		||||
//     description: 'Increase complexity of the commission',
 | 
			
		||||
//     pricePercent: 15,
 | 
			
		||||
//     price: 0,
 | 
			
		||||
//     priceRange: ""
 | 
			
		||||
//   },
 | 
			
		||||
//   highComplexity: {
 | 
			
		||||
//     name: 'High Complexity',
 | 
			
		||||
//     description: 'Highly increase complexity of the commission',
 | 
			
		||||
//     pricePercent: 30,
 | 
			
		||||
//     price: 0,
 | 
			
		||||
//     priceRange: ""
 | 
			
		||||
//   },
 | 
			
		||||
//   details: {
 | 
			
		||||
//     name: 'Details',
 | 
			
		||||
//     description: 'Add details to the commission (tails, eyes, ...)',
 | 
			
		||||
//     pricePercent: 0,
 | 
			
		||||
//     price: 0,
 | 
			
		||||
//     priceRange: "3#5"
 | 
			
		||||
//   },
 | 
			
		||||
//   shading: {
 | 
			
		||||
//     name: 'Shading',
 | 
			
		||||
//     description: 'Add shading to the commission',
 | 
			
		||||
//     pricePercent: 0,
 | 
			
		||||
//     price: 20,
 | 
			
		||||
//     priceRange: ""
 | 
			
		||||
//   }
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
export const rawCommissions = [
 | 
			
		||||
  {
 | 
			
		||||
    type: commissionTypes.sketch,
 | 
			
		||||
    options: [
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionOptions.headshot,
 | 
			
		||||
        price: commissionOptions.headshot.basePrice
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionOptions.halfbody,
 | 
			
		||||
        price: commissionOptions.halfbody.basePrice
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionOptions.fullbody,
 | 
			
		||||
        price: commissionOptions.fullbody.basePrice
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    extras: [
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.extraChar,
 | 
			
		||||
        pricePercent: commissionExtras.extraChar.pricePercent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.basicBackground,
 | 
			
		||||
        price: commissionExtras.basicBackground.price
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.abstractBackground,
 | 
			
		||||
        price: commissionExtras.abstractBackground.price
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.gradientBackground,
 | 
			
		||||
        price: commissionExtras.gradientBackground.price
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.customBackground,
 | 
			
		||||
        priceRange: commissionExtras.customBackground.priceRange
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.nsfw,
 | 
			
		||||
        pricePercent: commissionExtras.nsfw.pricePercent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.normalComplexity,
 | 
			
		||||
        pricePercent: commissionExtras.normalComplexity.pricePercent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.highComplexity,
 | 
			
		||||
        pricePercent: commissionExtras.highComplexity.pricePercent
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    examples: [
 | 
			
		||||
      '/examples/sketch1.png'
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: commissionTypes.flat,
 | 
			
		||||
    options: [
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionOptions.headshot,
 | 
			
		||||
        price: 18
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionOptions.halfbody,
 | 
			
		||||
        price: 22
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionOptions.fullbody,
 | 
			
		||||
        price: 28
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    extras: [
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.extraChar,
 | 
			
		||||
        pricePercent: commissionExtras.extraChar.pricePercent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.basicBackground,
 | 
			
		||||
        price: commissionExtras.basicBackground.price
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.abstractBackground,
 | 
			
		||||
        price: commissionExtras.abstractBackground.price
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.gradientBackground,
 | 
			
		||||
        price: commissionExtras.gradientBackground.price
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.customBackground,
 | 
			
		||||
        priceRange: commissionExtras.customBackground.priceRange
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.nsfw,
 | 
			
		||||
        pricePercent: commissionExtras.nsfw.pricePercent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.normalComplexity,
 | 
			
		||||
        pricePercent: commissionExtras.normalComplexity.pricePercent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.highComplexity,
 | 
			
		||||
        pricePercent: commissionExtras.highComplexity.pricePercent
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    examples: [
 | 
			
		||||
      '/examples/flat1.png',
 | 
			
		||||
      '/examples/flat2.png'
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: commissionTypes.shaded,
 | 
			
		||||
    options: [
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionOptions.headshot,
 | 
			
		||||
        price: 25
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionOptions.halfbody,
 | 
			
		||||
        price: 40
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionOptions.fullbody,
 | 
			
		||||
        price: 55
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    extras: [
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.extraChar,
 | 
			
		||||
        pricePercent: commissionExtras.extraChar.pricePercent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.basicBackground,
 | 
			
		||||
        price: commissionExtras.basicBackground.price
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.abstractBackground,
 | 
			
		||||
        price: commissionExtras.abstractBackground.price
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.gradientBackground,
 | 
			
		||||
        price: commissionExtras.gradientBackground.price
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.customBackground,
 | 
			
		||||
        priceRange: "20#100"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.nsfw,
 | 
			
		||||
        pricePercent: commissionExtras.nsfw.pricePercent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.normalComplexity,
 | 
			
		||||
        pricePercent: commissionExtras.normalComplexity.pricePercent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.highComplexity,
 | 
			
		||||
        pricePercent: commissionExtras.highComplexity.pricePercent
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    examples: [
 | 
			
		||||
      '/examples/shaded1.png'
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: commissionTypes.refsheet,
 | 
			
		||||
    options: [
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionOptions.fullbody,
 | 
			
		||||
        price: 25
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    extras: [
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.customBackground,
 | 
			
		||||
        priceRange: "20#100"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.nsfw,
 | 
			
		||||
        pricePercent: commissionExtras.nsfw.pricePercent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.normalComplexity,
 | 
			
		||||
        pricePercent: commissionExtras.normalComplexity.pricePercent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.highComplexity,
 | 
			
		||||
        pricePercent: commissionExtras.highComplexity.pricePercent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.details,
 | 
			
		||||
        priceRange: commissionExtras.details.priceRange
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: commissionExtras.shading,
 | 
			
		||||
        price: commissionExtras.shading.price
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    examples: [
 | 
			
		||||
      '/examples/refsheet1.png'
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
// export const rawCommissions = [
 | 
			
		||||
//   {
 | 
			
		||||
//     type: commissionTypes.sketch,
 | 
			
		||||
//     options: [
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionOptions.headshot,
 | 
			
		||||
//         price: commissionOptions.headshot.basePrice
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionOptions.halfbody,
 | 
			
		||||
//         price: commissionOptions.halfbody.basePrice
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionOptions.fullbody,
 | 
			
		||||
//         price: commissionOptions.fullbody.basePrice
 | 
			
		||||
//       }
 | 
			
		||||
//     ],
 | 
			
		||||
//     extras: [
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.extraChar,
 | 
			
		||||
//         pricePercent: commissionExtras.extraChar.pricePercent
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.basicBackground,
 | 
			
		||||
//         price: commissionExtras.basicBackground.price
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.abstractBackground,
 | 
			
		||||
//         price: commissionExtras.abstractBackground.price
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.gradientBackground,
 | 
			
		||||
//         price: commissionExtras.gradientBackground.price
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.customBackground,
 | 
			
		||||
//         priceRange: commissionExtras.customBackground.priceRange
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.nsfw,
 | 
			
		||||
//         pricePercent: commissionExtras.nsfw.pricePercent
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.normalComplexity,
 | 
			
		||||
//         pricePercent: commissionExtras.normalComplexity.pricePercent
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.highComplexity,
 | 
			
		||||
//         pricePercent: commissionExtras.highComplexity.pricePercent
 | 
			
		||||
//       },
 | 
			
		||||
//     ],
 | 
			
		||||
//     examples: [
 | 
			
		||||
//       '/examples/sketch1.png'
 | 
			
		||||
//     ]
 | 
			
		||||
//   },
 | 
			
		||||
//   {
 | 
			
		||||
//     type: commissionTypes.flat,
 | 
			
		||||
//     options: [
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionOptions.headshot,
 | 
			
		||||
//         price: 18
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionOptions.halfbody,
 | 
			
		||||
//         price: 22
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionOptions.fullbody,
 | 
			
		||||
//         price: 28
 | 
			
		||||
//       }
 | 
			
		||||
//     ],
 | 
			
		||||
//     extras: [
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.extraChar,
 | 
			
		||||
//         pricePercent: commissionExtras.extraChar.pricePercent
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.basicBackground,
 | 
			
		||||
//         price: commissionExtras.basicBackground.price
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.abstractBackground,
 | 
			
		||||
//         price: commissionExtras.abstractBackground.price
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.gradientBackground,
 | 
			
		||||
//         price: commissionExtras.gradientBackground.price
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.customBackground,
 | 
			
		||||
//         priceRange: commissionExtras.customBackground.priceRange
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.nsfw,
 | 
			
		||||
//         pricePercent: commissionExtras.nsfw.pricePercent
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.normalComplexity,
 | 
			
		||||
//         pricePercent: commissionExtras.normalComplexity.pricePercent
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.highComplexity,
 | 
			
		||||
//         pricePercent: commissionExtras.highComplexity.pricePercent
 | 
			
		||||
//       }
 | 
			
		||||
//     ],
 | 
			
		||||
//     examples: [
 | 
			
		||||
//       '/examples/flat1.png',
 | 
			
		||||
//       '/examples/flat2.png'
 | 
			
		||||
//     ]
 | 
			
		||||
//   },
 | 
			
		||||
//   {
 | 
			
		||||
//     type: commissionTypes.shaded,
 | 
			
		||||
//     options: [
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionOptions.headshot,
 | 
			
		||||
//         price: 25
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionOptions.halfbody,
 | 
			
		||||
//         price: 40
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionOptions.fullbody,
 | 
			
		||||
//         price: 55
 | 
			
		||||
//       }
 | 
			
		||||
//     ],
 | 
			
		||||
//     extras: [
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.extraChar,
 | 
			
		||||
//         pricePercent: commissionExtras.extraChar.pricePercent
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.basicBackground,
 | 
			
		||||
//         price: commissionExtras.basicBackground.price
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.abstractBackground,
 | 
			
		||||
//         price: commissionExtras.abstractBackground.price
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.gradientBackground,
 | 
			
		||||
//         price: commissionExtras.gradientBackground.price
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.customBackground,
 | 
			
		||||
//         priceRange: "20#100"
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.nsfw,
 | 
			
		||||
//         pricePercent: commissionExtras.nsfw.pricePercent
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.normalComplexity,
 | 
			
		||||
//         pricePercent: commissionExtras.normalComplexity.pricePercent
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.highComplexity,
 | 
			
		||||
//         pricePercent: commissionExtras.highComplexity.pricePercent
 | 
			
		||||
//       }
 | 
			
		||||
//     ],
 | 
			
		||||
//     examples: [
 | 
			
		||||
//       '/examples/shaded1.png'
 | 
			
		||||
//     ]
 | 
			
		||||
//   },
 | 
			
		||||
//   {
 | 
			
		||||
//     type: commissionTypes.refsheet,
 | 
			
		||||
//     options: [
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionOptions.fullbody,
 | 
			
		||||
//         price: 25
 | 
			
		||||
//       }
 | 
			
		||||
//     ],
 | 
			
		||||
//     extras: [
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.customBackground,
 | 
			
		||||
//         priceRange: "20#100"
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.nsfw,
 | 
			
		||||
//         pricePercent: commissionExtras.nsfw.pricePercent
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.normalComplexity,
 | 
			
		||||
//         pricePercent: commissionExtras.normalComplexity.pricePercent
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.highComplexity,
 | 
			
		||||
//         pricePercent: commissionExtras.highComplexity.pricePercent
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.details,
 | 
			
		||||
//         priceRange: commissionExtras.details.priceRange
 | 
			
		||||
//       },
 | 
			
		||||
//       {
 | 
			
		||||
//         type: commissionExtras.shading,
 | 
			
		||||
//         price: commissionExtras.shading.price
 | 
			
		||||
//       }
 | 
			
		||||
//     ],
 | 
			
		||||
//     examples: [
 | 
			
		||||
//       '/examples/refsheet1.png'
 | 
			
		||||
//     ]
 | 
			
		||||
//   }
 | 
			
		||||
// ]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								src/lib/prisma.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/lib/prisma.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
// import { PrismaClient } from '@/types/prisma'
 | 
			
		||||
// import { withAccelerate } from '@prisma/extension-accelerate'
 | 
			
		||||
 | 
			
		||||
import { PrismaClient } from "@/generated/prisma"
 | 
			
		||||
 | 
			
		||||
const globalForPrisma = global as unknown as { 
 | 
			
		||||
    prisma: PrismaClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const prisma = globalForPrisma.prisma || new PrismaClient()
 | 
			
		||||
 | 
			
		||||
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
 | 
			
		||||
 | 
			
		||||
export default prisma
 | 
			
		||||
@ -1,14 +1,14 @@
 | 
			
		||||
import { z } from "zod"
 | 
			
		||||
// import { z } from "zod"
 | 
			
		||||
 | 
			
		||||
export const commissionOrderSchema = z.object({
 | 
			
		||||
  type: z.enum(["sketch", "flat", "shaded", "refsheet"]),
 | 
			
		||||
  body: z.enum(["headshot", "portrait", "waistup", "fullbody"]).optional(),
 | 
			
		||||
  extras: z.array(z.string()).optional(),
 | 
			
		||||
  customExtras: z.string().optional(),
 | 
			
		||||
  description: z.string().min(10),
 | 
			
		||||
  contact: z.string().email("Please enter a valid email"),
 | 
			
		||||
  referenceFiles: z.array(z.any()).optional(), // for now just accept any File[]
 | 
			
		||||
  tosAccepted: z.literal(true), // must be checked
 | 
			
		||||
})
 | 
			
		||||
// export const commissionOrderSchema = z.object({
 | 
			
		||||
//   type: z.enum(["sketch", "flat", "shaded", "refsheet"]),
 | 
			
		||||
//   body: z.enum(["headshot", "portrait", "waistup", "fullbody"]).optional(),
 | 
			
		||||
//   extras: z.array(z.string()).optional(),
 | 
			
		||||
//   customExtras: z.string().optional(),
 | 
			
		||||
//   description: z.string().min(10),
 | 
			
		||||
//   contact: z.string().email("Please enter a valid email"),
 | 
			
		||||
//   referenceFiles: z.array(z.any()).optional(), // for now just accept any File[]
 | 
			
		||||
//   tosAccepted: z.literal(true), // must be checked
 | 
			
		||||
// })
 | 
			
		||||
 | 
			
		||||
export type commissionOrderSchema = z.infer<typeof commissionOrderSchema>
 | 
			
		||||
// export type commissionOrderSchema = z.infer<typeof commissionOrderSchema>
 | 
			
		||||
							
								
								
									
										10
									
								
								src/schemas/commissionOrder.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/schemas/commissionOrder.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
import * as z from "zod/v4"
 | 
			
		||||
 | 
			
		||||
export const commissionOrderSchema = z.object({
 | 
			
		||||
  typeId: z.string().min(1, "Please select a type"),
 | 
			
		||||
  optionId: z.string().min(1, "Please choose a base option"),
 | 
			
		||||
  extraIds: z.array(z.string()).optional(),
 | 
			
		||||
  customerName: z.string().min(2, "Enter your name"),
 | 
			
		||||
  customerEmail: z.string().email("Invalid email"),
 | 
			
		||||
  message: z.string().min(5, "Please describe what you want"),
 | 
			
		||||
})
 | 
			
		||||
@ -1,59 +1,59 @@
 | 
			
		||||
// types/commissions.ts
 | 
			
		||||
// // types/commissions.ts
 | 
			
		||||
 | 
			
		||||
export interface CommissionType {
 | 
			
		||||
  name: string
 | 
			
		||||
  description: string
 | 
			
		||||
}
 | 
			
		||||
// export interface CommissionType {
 | 
			
		||||
//   name: string
 | 
			
		||||
//   description: string
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
export interface CommissionOptionType {
 | 
			
		||||
  name: string
 | 
			
		||||
  basePrice: number
 | 
			
		||||
}
 | 
			
		||||
// export interface CommissionOptionType {
 | 
			
		||||
//   name: string
 | 
			
		||||
//   basePrice: number
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
export interface CommissionOption {
 | 
			
		||||
  type: CommissionOptionType
 | 
			
		||||
  price: number
 | 
			
		||||
}
 | 
			
		||||
// export interface CommissionOption {
 | 
			
		||||
//   type: CommissionOptionType
 | 
			
		||||
//   price: number
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
export interface CommissionExtraType {
 | 
			
		||||
  name: string
 | 
			
		||||
  description: string
 | 
			
		||||
  pricePercent?: number
 | 
			
		||||
  price?: number
 | 
			
		||||
  priceRange?: string
 | 
			
		||||
}
 | 
			
		||||
// export interface CommissionExtraType {
 | 
			
		||||
//   name: string
 | 
			
		||||
//   description: string
 | 
			
		||||
//   pricePercent?: number
 | 
			
		||||
//   price?: number
 | 
			
		||||
//   priceRange?: string
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
export interface CommissionExtra {
 | 
			
		||||
  type: CommissionExtraType
 | 
			
		||||
  price?: number
 | 
			
		||||
  pricePercent?: number
 | 
			
		||||
  priceRange?: string
 | 
			
		||||
}
 | 
			
		||||
// export interface CommissionExtra {
 | 
			
		||||
//   type: CommissionExtraType
 | 
			
		||||
//   price?: number
 | 
			
		||||
//   pricePercent?: number
 | 
			
		||||
//   priceRange?: string
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
export interface Commission {
 | 
			
		||||
  type: CommissionType
 | 
			
		||||
  options: CommissionOption[]
 | 
			
		||||
  extras: CommissionExtra[]
 | 
			
		||||
}
 | 
			
		||||
// export interface Commission {
 | 
			
		||||
//   type: CommissionType
 | 
			
		||||
//   options: CommissionOption[]
 | 
			
		||||
//   extras: CommissionExtra[]
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
export interface CommissionCardProps {
 | 
			
		||||
  type: {
 | 
			
		||||
    key: string
 | 
			
		||||
    name: string
 | 
			
		||||
    description: string
 | 
			
		||||
  }
 | 
			
		||||
  options: {
 | 
			
		||||
    key: string
 | 
			
		||||
    name: string
 | 
			
		||||
    price: number
 | 
			
		||||
  }[]
 | 
			
		||||
  extras: {
 | 
			
		||||
    key: string
 | 
			
		||||
    name: string
 | 
			
		||||
    description: string
 | 
			
		||||
    price?: number
 | 
			
		||||
    pricePercent?: number
 | 
			
		||||
    priceRange?: string
 | 
			
		||||
  }[],
 | 
			
		||||
  examples?: string[]
 | 
			
		||||
}
 | 
			
		||||
// export interface CommissionCardProps {
 | 
			
		||||
//   type: {
 | 
			
		||||
//     key: string
 | 
			
		||||
//     name: string
 | 
			
		||||
//     description: string
 | 
			
		||||
//   }
 | 
			
		||||
//   options: {
 | 
			
		||||
//     key: string
 | 
			
		||||
//     name: string
 | 
			
		||||
//     price: number
 | 
			
		||||
//   }[]
 | 
			
		||||
//   extras: {
 | 
			
		||||
//     key: string
 | 
			
		||||
//     name: string
 | 
			
		||||
//     description: string
 | 
			
		||||
//     price?: number
 | 
			
		||||
//     pricePercent?: number
 | 
			
		||||
//     priceRange?: string
 | 
			
		||||
//   }[],
 | 
			
		||||
//   examples?: string[]
 | 
			
		||||
// }
 | 
			
		||||
							
								
								
									
										49
									
								
								src/utils/calculatePrice.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/utils/calculatePrice.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
			
		||||
// export function calculatePrice(
 | 
			
		||||
//   option: { price?: number; pricePercent?: number; priceRange?: string },
 | 
			
		||||
//   extras: { price?: number; pricePercent?: number; priceRange?: string }[]
 | 
			
		||||
// ): number | [number, number] {
 | 
			
		||||
//   const base = option.price ?? 0
 | 
			
		||||
 | 
			
		||||
//   let total = base
 | 
			
		||||
//   let hasRange = false
 | 
			
		||||
//   let min = base
 | 
			
		||||
//   let max = base
 | 
			
		||||
 | 
			
		||||
//   for (const ext of extras) {
 | 
			
		||||
//     if (ext.price !== undefined) {
 | 
			
		||||
//       total += ext.price
 | 
			
		||||
//       min += ext.price
 | 
			
		||||
//       max += ext.price
 | 
			
		||||
//     } else if (ext.pricePercent !== undefined) {
 | 
			
		||||
//       const delta = base * (ext.pricePercent / 100)
 | 
			
		||||
//       total += delta
 | 
			
		||||
//       min += delta
 | 
			
		||||
//       max += delta
 | 
			
		||||
//     } else if (ext.priceRange) {
 | 
			
		||||
//       const [rMin, rMax] = ext.priceRange.split("–").map(Number)
 | 
			
		||||
//       hasRange = true
 | 
			
		||||
//       min += rMin
 | 
			
		||||
//       max += rMax
 | 
			
		||||
//     }
 | 
			
		||||
//   }
 | 
			
		||||
 | 
			
		||||
//   return hasRange ? [min, max] : total
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type PriceSource = {
 | 
			
		||||
  price?: number | null
 | 
			
		||||
  pricePercent?: number | null
 | 
			
		||||
  priceRange?: string | null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function calculatePrice(source: PriceSource, base: number): number {
 | 
			
		||||
  if (source.price != null) return source.price
 | 
			
		||||
  if (source.pricePercent != null) return base * (source.pricePercent / 100)
 | 
			
		||||
  if (source.priceRange) {
 | 
			
		||||
    const parts = source.priceRange.split("–").map(Number)
 | 
			
		||||
    const max = Math.max(...parts)
 | 
			
		||||
    return isNaN(max) ? 0 : max
 | 
			
		||||
  }
 | 
			
		||||
  return 0
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user