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
|
||||
@ -47,3 +14,35 @@ export function calculatePrice(source: PriceSource, base: number): number {
|
||||
}
|
||||
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