Add custom inputs to commission types
This commit is contained in:
		
							
								
								
									
										126
									
								
								ToDo.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								ToDo.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,126 @@
 | 
			
		||||
# ToDo List
 | 
			
		||||
 | 
			
		||||
## ✅ Commission System TODO
 | 
			
		||||
 | 
			
		||||
### 🎨 Commission Request Form
 | 
			
		||||
- [x] Commission order form UI
 | 
			
		||||
- [ ] Form submission logic
 | 
			
		||||
- [ ] File upload with storage (e.g. MinIO)
 | 
			
		||||
- [ ] Store form data in Prisma
 | 
			
		||||
- [ ] Confirmation / success page after submission
 | 
			
		||||
- [ ] Add optional fields (contact method, payment method, deadline, etc.)
 | 
			
		||||
- [ ] Add streaming/video preferences
 | 
			
		||||
- [ ] Add privacy & crediting preferences
 | 
			
		||||
- [ ] Admin-configurable field visibility / requirements (future)
 | 
			
		||||
 | 
			
		||||
### 📄 Pages
 | 
			
		||||
- [x] Terms of Service page
 | 
			
		||||
- [ ] Public commission info / landing page
 | 
			
		||||
- [ ] Commission type list with filters
 | 
			
		||||
- [ ] Single commission type preview (with examples)
 | 
			
		||||
- [ ] Commission request success page
 | 
			
		||||
- [ ] FAQ or Help page
 | 
			
		||||
- [ ] Page for **custom offers / YCHs**  
 | 
			
		||||
  - [ ] YCH listing grid  
 | 
			
		||||
  - [ ] YCH details page (slots, price, preview image)  
 | 
			
		||||
  - [ ] YCH claim form  
 | 
			
		||||
  - [ ] Status display (e.g. "slot taken", "open", "closed")  
 | 
			
		||||
 | 
			
		||||
### 🔐 Admin Panel
 | 
			
		||||
- [x] Commission type create/edit form
 | 
			
		||||
- [ ] Commission option/extra CRUD UI
 | 
			
		||||
- [ ] View list of commission requests
 | 
			
		||||
- [ ] View single request details
 | 
			
		||||
- [ ] Update request status (pending, accepted, rejected, etc.)
 | 
			
		||||
- [ ] Filter and sort commission requests
 | 
			
		||||
- [ ] View submitted files / references
 | 
			
		||||
- [ ] Tag, flag, or star requests for tracking
 | 
			
		||||
- [ ] Delete / archive requests
 | 
			
		||||
 | 
			
		||||
### 📦 Backend & Actions
 | 
			
		||||
- [ ] Submit commission request server action
 | 
			
		||||
- [ ] Upload reference image(s) action
 | 
			
		||||
- [ ] Create YCH offer action
 | 
			
		||||
- [ ] Claim YCH slot action
 | 
			
		||||
- [ ] Send confirmation email (optional)
 | 
			
		||||
- [ ] Notify artist via email or admin panel
 | 
			
		||||
- [ ] Export commission request to JSON or PDF (optional)
 | 
			
		||||
 | 
			
		||||
### 🧩 Extras (Later)
 | 
			
		||||
- [ ] Kanban-style board for request tracking
 | 
			
		||||
- [ ] Queue page (public or admin)
 | 
			
		||||
- [ ] Markdown/WYSIWYG editor for ToS and descriptions
 | 
			
		||||
- [ ] Analytics for commission activity
 | 
			
		||||
- [ ] iCal or Notion export
 | 
			
		||||
- [ ] Stripe or Ko-fi payment integration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Customer Commission Ordering Form
 | 
			
		||||
 | 
			
		||||
### ✅ Essential Fields
 | 
			
		||||
- [x] Commission type  
 | 
			
		||||
- [x] Base option (e.g. headshot, fullbody)  
 | 
			
		||||
- [x] Selected extras  
 | 
			
		||||
- [x] Customer name  
 | 
			
		||||
- [x] Customer email  
 | 
			
		||||
- [x] Commission description / idea  
 | 
			
		||||
- [x] Reference image upload  
 | 
			
		||||
- [x] Confirm Terms of Service  
 | 
			
		||||
 | 
			
		||||
### 🧠 Project Details
 | 
			
		||||
- [ ] Character description / bio  
 | 
			
		||||
- [ ] Pose or mood suggestions  
 | 
			
		||||
- [ ] Background description  
 | 
			
		||||
- [ ] Scene / environment ideas  
 | 
			
		||||
- [ ] Color palette preferences  
 | 
			
		||||
- [ ] Inspiration images or links  
 | 
			
		||||
- [ ] “I’m unsure, open to ideas” checkbox  
 | 
			
		||||
 | 
			
		||||
### 📅 Timeline & Budget
 | 
			
		||||
- [ ] Preferred deadline / latest delivery date  
 | 
			
		||||
- [ ] “Flexible deadline” checkbox  
 | 
			
		||||
- [ ] Budget or max amount willing to pay  
 | 
			
		||||
- [ ] “Urgent / rush commission” checkbox  
 | 
			
		||||
 | 
			
		||||
### 💬 Contact Preferences
 | 
			
		||||
- [ ] Preferred contact method  
 | 
			
		||||
  - [ ] Email  
 | 
			
		||||
  - [ ] Telegram  
 | 
			
		||||
  - [ ] Twitter  
 | 
			
		||||
  - [ ] Discord  
 | 
			
		||||
  - [ ] Other (input)  
 | 
			
		||||
- [ ] Contact handle / username  
 | 
			
		||||
- [ ] Timezone or active hours (optional)  
 | 
			
		||||
 | 
			
		||||
### 💸 Payment Preferences
 | 
			
		||||
- [ ] Preferred payment method  
 | 
			
		||||
  - [ ] PayPal  
 | 
			
		||||
  - [ ] Ko-fi  
 | 
			
		||||
  - [ ] Stripe  
 | 
			
		||||
  - [ ] Bank transfer  
 | 
			
		||||
  - [ ] Crypto  
 | 
			
		||||
  - [ ] Other (input)  
 | 
			
		||||
- [ ] Payment notes (optional)  
 | 
			
		||||
- [ ] Invoice required?  
 | 
			
		||||
- [ ] “I want to pay upfront” checkbox  
 | 
			
		||||
 | 
			
		||||
### 📽 Streaming / Video
 | 
			
		||||
- [ ] I’d like to watch on stream  
 | 
			
		||||
- [ ] Please record a speedpaint / timelapse  
 | 
			
		||||
- [ ] OK to share the stream publicly  
 | 
			
		||||
- [ ] OK to post timelapse/video publicly  
 | 
			
		||||
 | 
			
		||||
### 🌐 Visibility & Rights
 | 
			
		||||
- [ ] NSFW content  
 | 
			
		||||
- [ ] Gore / heavy themes  
 | 
			
		||||
- [ ] “Please keep this private” checkbox  
 | 
			
		||||
- [ ] “OK to share on social media / portfolio”  
 | 
			
		||||
- [ ] Credit preference  
 | 
			
		||||
  - [ ] Use my name  
 | 
			
		||||
  - [ ] Use my alias  
 | 
			
		||||
  - [ ] Stay anonymous  
 | 
			
		||||
- [ ] Commercial use intended  
 | 
			
		||||
 | 
			
		||||
### 🗂 Optional / Admin Fields
 | 
			
		||||
- [ ] Commission title / project name  
 | 
			
		||||
- [ ] Additional notes / anything else to mention  
 | 
			
		||||
							
								
								
									
										294
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										294
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -27,6 +27,7 @@
 | 
			
		||||
        "react-dom": "^19.0.0",
 | 
			
		||||
        "react-hook-form": "^7.60.0",
 | 
			
		||||
        "react-markdown": "^10.1.0",
 | 
			
		||||
        "remark-gfm": "^4.0.1",
 | 
			
		||||
        "sonner": "^2.0.6",
 | 
			
		||||
        "tailwind-merge": "^3.3.1",
 | 
			
		||||
        "zod": "^3.25.74"
 | 
			
		||||
@ -5899,6 +5900,16 @@
 | 
			
		||||
        "@jridgewell/sourcemap-codec": "^1.5.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/markdown-table": {
 | 
			
		||||
      "version": "3.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "github",
 | 
			
		||||
        "url": "https://github.com/sponsors/wooorm"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/math-intrinsics": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
 | 
			
		||||
@ -5909,6 +5920,34 @@
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mdast-util-find-and-replace": {
 | 
			
		||||
      "version": "3.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/mdast": "^4.0.0",
 | 
			
		||||
        "escape-string-regexp": "^5.0.0",
 | 
			
		||||
        "unist-util-is": "^6.0.0",
 | 
			
		||||
        "unist-util-visit-parents": "^6.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
 | 
			
		||||
      "version": "5.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mdast-util-from-markdown": {
 | 
			
		||||
      "version": "2.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
 | 
			
		||||
@ -5933,6 +5972,107 @@
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mdast-util-gfm": {
 | 
			
		||||
      "version": "3.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "mdast-util-from-markdown": "^2.0.0",
 | 
			
		||||
        "mdast-util-gfm-autolink-literal": "^2.0.0",
 | 
			
		||||
        "mdast-util-gfm-footnote": "^2.0.0",
 | 
			
		||||
        "mdast-util-gfm-strikethrough": "^2.0.0",
 | 
			
		||||
        "mdast-util-gfm-table": "^2.0.0",
 | 
			
		||||
        "mdast-util-gfm-task-list-item": "^2.0.0",
 | 
			
		||||
        "mdast-util-to-markdown": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mdast-util-gfm-autolink-literal": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/mdast": "^4.0.0",
 | 
			
		||||
        "ccount": "^2.0.0",
 | 
			
		||||
        "devlop": "^1.0.0",
 | 
			
		||||
        "mdast-util-find-and-replace": "^3.0.0",
 | 
			
		||||
        "micromark-util-character": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mdast-util-gfm-footnote": {
 | 
			
		||||
      "version": "2.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/mdast": "^4.0.0",
 | 
			
		||||
        "devlop": "^1.1.0",
 | 
			
		||||
        "mdast-util-from-markdown": "^2.0.0",
 | 
			
		||||
        "mdast-util-to-markdown": "^2.0.0",
 | 
			
		||||
        "micromark-util-normalize-identifier": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mdast-util-gfm-strikethrough": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/mdast": "^4.0.0",
 | 
			
		||||
        "mdast-util-from-markdown": "^2.0.0",
 | 
			
		||||
        "mdast-util-to-markdown": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mdast-util-gfm-table": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/mdast": "^4.0.0",
 | 
			
		||||
        "devlop": "^1.0.0",
 | 
			
		||||
        "markdown-table": "^3.0.0",
 | 
			
		||||
        "mdast-util-from-markdown": "^2.0.0",
 | 
			
		||||
        "mdast-util-to-markdown": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mdast-util-gfm-task-list-item": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/mdast": "^4.0.0",
 | 
			
		||||
        "devlop": "^1.0.0",
 | 
			
		||||
        "mdast-util-from-markdown": "^2.0.0",
 | 
			
		||||
        "mdast-util-to-markdown": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mdast-util-mdx-expression": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
 | 
			
		||||
@ -6141,6 +6281,127 @@
 | 
			
		||||
        "micromark-util-types": "^2.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/micromark-extension-gfm": {
 | 
			
		||||
      "version": "3.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "micromark-extension-gfm-autolink-literal": "^2.0.0",
 | 
			
		||||
        "micromark-extension-gfm-footnote": "^2.0.0",
 | 
			
		||||
        "micromark-extension-gfm-strikethrough": "^2.0.0",
 | 
			
		||||
        "micromark-extension-gfm-table": "^2.0.0",
 | 
			
		||||
        "micromark-extension-gfm-tagfilter": "^2.0.0",
 | 
			
		||||
        "micromark-extension-gfm-task-list-item": "^2.0.0",
 | 
			
		||||
        "micromark-util-combine-extensions": "^2.0.0",
 | 
			
		||||
        "micromark-util-types": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/micromark-extension-gfm-autolink-literal": {
 | 
			
		||||
      "version": "2.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "micromark-util-character": "^2.0.0",
 | 
			
		||||
        "micromark-util-sanitize-uri": "^2.0.0",
 | 
			
		||||
        "micromark-util-symbol": "^2.0.0",
 | 
			
		||||
        "micromark-util-types": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/micromark-extension-gfm-footnote": {
 | 
			
		||||
      "version": "2.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "devlop": "^1.0.0",
 | 
			
		||||
        "micromark-core-commonmark": "^2.0.0",
 | 
			
		||||
        "micromark-factory-space": "^2.0.0",
 | 
			
		||||
        "micromark-util-character": "^2.0.0",
 | 
			
		||||
        "micromark-util-normalize-identifier": "^2.0.0",
 | 
			
		||||
        "micromark-util-sanitize-uri": "^2.0.0",
 | 
			
		||||
        "micromark-util-symbol": "^2.0.0",
 | 
			
		||||
        "micromark-util-types": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/micromark-extension-gfm-strikethrough": {
 | 
			
		||||
      "version": "2.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "devlop": "^1.0.0",
 | 
			
		||||
        "micromark-util-chunked": "^2.0.0",
 | 
			
		||||
        "micromark-util-classify-character": "^2.0.0",
 | 
			
		||||
        "micromark-util-resolve-all": "^2.0.0",
 | 
			
		||||
        "micromark-util-symbol": "^2.0.0",
 | 
			
		||||
        "micromark-util-types": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/micromark-extension-gfm-table": {
 | 
			
		||||
      "version": "2.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "devlop": "^1.0.0",
 | 
			
		||||
        "micromark-factory-space": "^2.0.0",
 | 
			
		||||
        "micromark-util-character": "^2.0.0",
 | 
			
		||||
        "micromark-util-symbol": "^2.0.0",
 | 
			
		||||
        "micromark-util-types": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/micromark-extension-gfm-tagfilter": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "micromark-util-types": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/micromark-extension-gfm-task-list-item": {
 | 
			
		||||
      "version": "2.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "devlop": "^1.0.0",
 | 
			
		||||
        "micromark-factory-space": "^2.0.0",
 | 
			
		||||
        "micromark-util-character": "^2.0.0",
 | 
			
		||||
        "micromark-util-symbol": "^2.0.0",
 | 
			
		||||
        "micromark-util-types": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/micromark-factory-destination": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
 | 
			
		||||
@ -7360,6 +7621,24 @@
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/remark-gfm": {
 | 
			
		||||
      "version": "4.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/mdast": "^4.0.0",
 | 
			
		||||
        "mdast-util-gfm": "^3.0.0",
 | 
			
		||||
        "micromark-extension-gfm": "^3.0.0",
 | 
			
		||||
        "remark-parse": "^11.0.0",
 | 
			
		||||
        "remark-stringify": "^11.0.0",
 | 
			
		||||
        "unified": "^11.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/remark-parse": {
 | 
			
		||||
      "version": "11.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
 | 
			
		||||
@ -7393,6 +7672,21 @@
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/remark-stringify": {
 | 
			
		||||
      "version": "11.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/mdast": "^4.0.0",
 | 
			
		||||
        "mdast-util-to-markdown": "^2.0.0",
 | 
			
		||||
        "unified": "^11.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/resolve": {
 | 
			
		||||
      "version": "1.22.10",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
 | 
			
		||||
 | 
			
		||||
@ -28,6 +28,7 @@
 | 
			
		||||
    "react-dom": "^19.0.0",
 | 
			
		||||
    "react-hook-form": "^7.60.0",
 | 
			
		||||
    "react-markdown": "^10.1.0",
 | 
			
		||||
    "remark-gfm": "^4.0.1",
 | 
			
		||||
    "sonner": "^2.0.6",
 | 
			
		||||
    "tailwind-merge": "^3.3.1",
 | 
			
		||||
    "zod": "^3.25.74"
 | 
			
		||||
 | 
			
		||||
@ -24,8 +24,9 @@ model CommissionType {
 | 
			
		||||
 | 
			
		||||
  description String?
 | 
			
		||||
 | 
			
		||||
  options CommissionTypeOption[]
 | 
			
		||||
  extras  CommissionTypeExtra[]
 | 
			
		||||
  options      CommissionTypeOption[]
 | 
			
		||||
  extras       CommissionTypeExtra[]
 | 
			
		||||
  customInputs CommissionTypeCustomInput[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model CommissionOption {
 | 
			
		||||
@ -54,6 +55,18 @@ model CommissionExtra {
 | 
			
		||||
  types CommissionTypeExtra[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 CommissionTypeOption {
 | 
			
		||||
  id        String   @id @default(cuid())
 | 
			
		||||
  createdAt DateTime @default(now())
 | 
			
		||||
@ -92,6 +105,25 @@ model CommissionTypeExtra {
 | 
			
		||||
  @@unique([typeId, extraId])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 TermsOfService {
 | 
			
		||||
  id        String   @id @default(cuid())
 | 
			
		||||
  createdAt DateTime @default(now())
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ export default async function CommissionsPage() {
 | 
			
		||||
    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" }],
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
@ -160,6 +160,17 @@
 | 
			
		||||
  @apply underline text-primary hover:text-primary/80;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.markdown li:has(input[type="checkbox"]) {
 | 
			
		||||
  @apply list-none pl-0 items-start gap-2 mb-2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.markdown input[type="checkbox"] {
 | 
			
		||||
  @apply mt-1 h-4 w-4 shrink-0 rounded border border-border bg-background text-primary accent-primary cursor-default;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.markdown input[type="checkbox"]:checked + * {
 | 
			
		||||
  @apply line-through text-muted-foreground;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@layer base {
 | 
			
		||||
  * {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								src/app/todo/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/app/todo/page.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
import fs from "fs/promises";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import ReactMarkdown from "react-markdown";
 | 
			
		||||
import remarkGfm from "remark-gfm";
 | 
			
		||||
 | 
			
		||||
export default async function TodoPage() {
 | 
			
		||||
  const filePath = path.join(process.cwd(), "ToDo.md")
 | 
			
		||||
  const content = await fs.readFile(filePath, "utf-8")
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <main className="markdown max-w-3xl mx-auto p-6">
 | 
			
		||||
      <ReactMarkdown
 | 
			
		||||
        remarkPlugins={[remarkGfm]}
 | 
			
		||||
      >
 | 
			
		||||
        {content}
 | 
			
		||||
      </ReactMarkdown>
 | 
			
		||||
    </main>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -11,9 +11,9 @@ import {
 | 
			
		||||
} 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 { CommissionCustomInput, CommissionExtra, CommissionOption, CommissionType, CommissionTypeCustomInput, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma"
 | 
			
		||||
import { commissionOrderSchema } from "@/schemas/commissionOrder"
 | 
			
		||||
import { calculatePrice } from "@/utils/calculatePrice"
 | 
			
		||||
import { calculatePriceRange } from "@/utils/calculatePrice"
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod"
 | 
			
		||||
import Link from "next/link"
 | 
			
		||||
import { useMemo, useState } from "react"
 | 
			
		||||
@ -24,6 +24,7 @@ import { FileDropzone } from "./FileDropzone"
 | 
			
		||||
type CommissionTypeWithRelations = CommissionType & {
 | 
			
		||||
  options: (CommissionTypeOption & { option: CommissionOption })[]
 | 
			
		||||
  extras: (CommissionTypeExtra & { extra: CommissionExtra })[]
 | 
			
		||||
  customInputs: (CommissionTypeCustomInput & { customInput: CommissionCustomInput })[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
@ -64,20 +65,13 @@ export function CommissionOrderForm({ types }: Props) {
 | 
			
		||||
    [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
 | 
			
		||||
  const [minPrice, maxPrice] = useMemo(() => {
 | 
			
		||||
    return calculatePriceRange(selectedOption, selectedExtras)
 | 
			
		||||
  }, [selectedOption, selectedExtras])
 | 
			
		||||
 | 
			
		||||
  async function onSubmit(values: z.infer<typeof commissionOrderSchema>) {
 | 
			
		||||
    console.log("Submit:", { ...values, files })
 | 
			
		||||
    const { customFields, ...rest } = values
 | 
			
		||||
    console.log("Submit:", { ...rest, customFields, files })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
@ -173,6 +167,8 @@ export function CommissionOrderForm({ types }: Props) {
 | 
			
		||||
          </>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
@ -220,6 +216,54 @@ export function CommissionOrderForm({ types }: Props) {
 | 
			
		||||
          )}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        {selectedType && selectedType.customInputs.length > 0 && (
 | 
			
		||||
          <div className="space-y-4">
 | 
			
		||||
            {selectedType.customInputs.map((input) => {
 | 
			
		||||
              const name = `customFields.${input.customInput.name}`
 | 
			
		||||
              return (
 | 
			
		||||
                <FormField
 | 
			
		||||
                  key={input.id}
 | 
			
		||||
                  control={form.control}
 | 
			
		||||
                  name={name as `customFields.${string}`}
 | 
			
		||||
                  render={({ field }) => (
 | 
			
		||||
                    <FormItem>
 | 
			
		||||
                      <FormLabel>{input.label}</FormLabel>
 | 
			
		||||
                      <FormControl>
 | 
			
		||||
                        {input.inputType === "textarea" ? (
 | 
			
		||||
                          <Textarea {...field} rows={3} />
 | 
			
		||||
                        ) : input.inputType === "number" ? (
 | 
			
		||||
                          <Input type="number" {...field} />
 | 
			
		||||
                        ) : input.inputType === "checkbox" ? (
 | 
			
		||||
                          <input
 | 
			
		||||
                            type="checkbox"
 | 
			
		||||
                            checked={field.value ?? false}
 | 
			
		||||
                            onChange={(e) => field.onChange(e.target.checked)}
 | 
			
		||||
                          />
 | 
			
		||||
                        ) : input.inputType === "date" ? (
 | 
			
		||||
                          <Input type="date" {...field} />
 | 
			
		||||
                        ) : input.inputType === "select" ? (
 | 
			
		||||
                          // Placeholder select – populate with options if needed
 | 
			
		||||
                          <select
 | 
			
		||||
                            {...field}
 | 
			
		||||
                            className="border rounded px-2 py-1 w-full"
 | 
			
		||||
                          >
 | 
			
		||||
                            <option value="">Please select</option>
 | 
			
		||||
                            <option value="example1">Example 1</option>
 | 
			
		||||
                            <option value="example2">Example 2</option>
 | 
			
		||||
                          </select>
 | 
			
		||||
                        ) : (
 | 
			
		||||
                          <Input {...field} />
 | 
			
		||||
                        )}
 | 
			
		||||
                      </FormControl>
 | 
			
		||||
                      <FormMessage />
 | 
			
		||||
                    </FormItem>
 | 
			
		||||
                  )}
 | 
			
		||||
                />
 | 
			
		||||
              )
 | 
			
		||||
            })}
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        <FormItem>
 | 
			
		||||
          <FormLabel>Reference Images</FormLabel>
 | 
			
		||||
          <FormControl>
 | 
			
		||||
@ -237,7 +281,10 @@ export function CommissionOrderForm({ types }: Props) {
 | 
			
		||||
        </FormItem>
 | 
			
		||||
 | 
			
		||||
        <div className="text-lg font-semibold">
 | 
			
		||||
          Estimated Price: €{price.toFixed(2)}
 | 
			
		||||
          Estimated Price:{" "}
 | 
			
		||||
          {minPrice === maxPrice
 | 
			
		||||
            ? `€${minPrice.toFixed(2)}`
 | 
			
		||||
            : `€${minPrice.toFixed(2)} – €${maxPrice.toFixed(2)}`}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div className="text-muted-foreground">
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,11 @@ export default function TopNav() {
 | 
			
		||||
            <Link href="/tos">Terms of Service</Link>
 | 
			
		||||
          </NavigationMenuLink>
 | 
			
		||||
        </NavigationMenuItem>
 | 
			
		||||
        <NavigationMenuItem>
 | 
			
		||||
          <NavigationMenuLink asChild className={navigationMenuTriggerStyle()}>
 | 
			
		||||
            <Link href="/todo">todo (temp)</Link>
 | 
			
		||||
          </NavigationMenuLink>
 | 
			
		||||
        </NavigationMenuItem>
 | 
			
		||||
      </NavigationMenuList>
 | 
			
		||||
    </NavigationMenu>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ 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(),
 | 
			
		||||
  customFields: z.record(z.string(), z.any()).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,36 +1,3 @@
 | 
			
		||||
// 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
 | 
			
		||||
@ -46,4 +13,36 @@ export function calculatePrice(source: PriceSource, base: number): number {
 | 
			
		||||
    return isNaN(max) ? 0 : max
 | 
			
		||||
  }
 | 
			
		||||
  return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function calculatePriceRange(
 | 
			
		||||
  baseSource: PriceSource | undefined,
 | 
			
		||||
  extras: PriceSource[]
 | 
			
		||||
): [number, number] {
 | 
			
		||||
  if (!baseSource) return [0, 0]
 | 
			
		||||
 | 
			
		||||
  const base = calculatePrice(baseSource, 0)
 | 
			
		||||
 | 
			
		||||
  let minExtra = 0
 | 
			
		||||
  let maxExtra = 0
 | 
			
		||||
 | 
			
		||||
  for (const extra of extras) {
 | 
			
		||||
    if (extra.price != null) {
 | 
			
		||||
      minExtra += extra.price
 | 
			
		||||
      maxExtra += extra.price
 | 
			
		||||
    } else if (extra.pricePercent != null) {
 | 
			
		||||
      const val = base * (extra.pricePercent / 100)
 | 
			
		||||
      minExtra += val
 | 
			
		||||
      maxExtra += val
 | 
			
		||||
    } else if (extra.priceRange) {
 | 
			
		||||
      const [minStr, maxStr] = extra.priceRange.split("–")
 | 
			
		||||
      const min = Number(minStr)
 | 
			
		||||
      const max = Number(maxStr)
 | 
			
		||||
 | 
			
		||||
      if (!isNaN(min)) minExtra += min
 | 
			
		||||
      if (!isNaN(max)) maxExtra += max
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return [base + minExtra, base + maxExtra]
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user