Spaces:
Build error
Build error
Upload vendor-contract-builder/page.tsx
Browse files- vendor-contract-builder/page.tsx +201 -0
vendor-contract-builder/page.tsx
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client'
|
| 2 |
+
import { useState } from 'react'
|
| 3 |
+
|
| 4 |
+
interface Deliverable { id:string; desc:string; due:string; qty:number; criteria:string }
|
| 5 |
+
interface Section { id:string; title:string; content:string; isDeliverables:boolean }
|
| 6 |
+
|
| 7 |
+
export default function VendorContractBuilder() {
|
| 8 |
+
const [clientName, setClientName] = useState('Amaya & Ruwan')
|
| 9 |
+
const [contractType, setContractType] = useState('Full Day Photography')
|
| 10 |
+
const [amount, setAmount] = useState('150000')
|
| 11 |
+
const [sections, setSections] = useState<Section[]>([
|
| 12 |
+
{ id:'1', title:'Scope of Services', content:'The Photographer shall provide professional wedding photography services on the wedding date, including pre-ceremony and reception coverage.', isDeliverables:false },
|
| 13 |
+
{ id:'2', title:'Deliverables', content:'', isDeliverables:true },
|
| 14 |
+
{ id:'3', title:'Timeline & Schedule', content:'Booking confirmation within 5 days. Pre-wedding consultation 2 weeks before. Full gallery delivery within 30 days post-event.', isDeliverables:false },
|
| 15 |
+
{ id:'4', title:'Payment Terms', content:`Total contract value: Rs. ${parseInt(amount).toLocaleString()}. Deposit of 33% due on signing. Balance due 7 days before event.`, isDeliverables:false },
|
| 16 |
+
{ id:'5', title:'Cancellation Policy', content:'Cancellation >60 days before: deposit forfeited. Within 60 days: 50% due. Within 30 days: full amount due.', isDeliverables:false },
|
| 17 |
+
])
|
| 18 |
+
|
| 19 |
+
const [deliverables, setDeliverables] = useState<Deliverable[]>([
|
| 20 |
+
{ id:'d1', desc:'Edited high-resolution digital images', due:'30 days post-event', qty:400, criteria:'Professional quality edits, color-corrected' },
|
| 21 |
+
{ id:'d2', desc:'Online gallery', due:'35 days post-event', qty:1, criteria:'Password protected, accessible for 12 months' },
|
| 22 |
+
{ id:'d3', desc:'USB drive with full-resolution images', due:'40 days post-event', qty:1, criteria:'Branded USB, all RAW + edited files' },
|
| 23 |
+
{ id:'d4', desc:'Premium layflat photo album', due:'60 days post-event', qty:1, criteria:'30 pages minimum, premium paper, leather cover' },
|
| 24 |
+
])
|
| 25 |
+
|
| 26 |
+
const [selectedTemplate, setSelectedTemplate] = useState('photography')
|
| 27 |
+
const [showPreview, setShowPreview] = useState(false)
|
| 28 |
+
|
| 29 |
+
const templates = [
|
| 30 |
+
{ id:'photography', name:'Photography Contract', desc:'Standard coverage, deliverables, and usage rights' },
|
| 31 |
+
{ id:'venue', name:'Venue Contract', desc:'Ceremony & reception, guest capacity, amenities' },
|
| 32 |
+
{ id:'catering', name:'Catering Contract', desc:'Menu selection, guest count, dietary accommodations' },
|
| 33 |
+
{ id:'florist', name:'Floral Design Contract', desc:'Arrangements, seasonal blooms, setup & teardown' },
|
| 34 |
+
]
|
| 35 |
+
|
| 36 |
+
const addSection = () => setSections([...sections,{id:Date.now().toString(),title:'New Section',content:'',isDeliverables:false}])
|
| 37 |
+
const addDeliverable = () => setDeliverables([...deliverables,{id:Date.now().toString(),desc:'New deliverable',due:'TBD',qty:1,criteria:''}])
|
| 38 |
+
|
| 39 |
+
return (
|
| 40 |
+
<div className="min-h-screen bg-ivory-50">
|
| 41 |
+
<div className="wedding-section py-8">
|
| 42 |
+
<div className="flex items-center justify-between mb-8">
|
| 43 |
+
<div>
|
| 44 |
+
<h1 className="font-heading text-3xl font-semibold text-charcoal-700">{showPreview?'Preview Contract':'Build Contract'}</h1>
|
| 45 |
+
<p className="text-charcoal-400 mt-1">Create a legally binding agreement with audit trail</p>
|
| 46 |
+
</div>
|
| 47 |
+
<div className="flex gap-3">
|
| 48 |
+
<select value={selectedTemplate} onChange={e=>setSelectedTemplate(e.target.value)}
|
| 49 |
+
className="wedding-input w-auto text-sm">
|
| 50 |
+
<option value="">Blank contract</option>
|
| 51 |
+
{templates.map(t=><option key={t.id} value={t.id}>{t.name}</option>)}
|
| 52 |
+
</select>
|
| 53 |
+
<button onClick={()=>setShowPreview(!showPreview)} className="wedding-btn-outline text-sm">
|
| 54 |
+
{showPreview?'← Edit':'Preview →'}
|
| 55 |
+
</button>
|
| 56 |
+
<button className="wedding-btn-gold text-sm">✉️ Send to Client</button>
|
| 57 |
+
</div>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
{!showPreview ? (
|
| 61 |
+
<div className="grid lg:grid-cols-3 gap-6">
|
| 62 |
+
{/* Left: Contract builder */}
|
| 63 |
+
<div className="lg:col-span-2 space-y-6">
|
| 64 |
+
{/* Header */}
|
| 65 |
+
<div className="wedding-card p-6">
|
| 66 |
+
<div className="grid sm:grid-cols-2 gap-4">
|
| 67 |
+
<div>
|
| 68 |
+
<label className="text-xs font-semibold text-charcoal-500 uppercase tracking-wider mb-1 block">Client</label>
|
| 69 |
+
<input value={clientName} onChange={e=>setClientName(e.target.value)} className="wedding-input" />
|
| 70 |
+
</div>
|
| 71 |
+
<div>
|
| 72 |
+
<label className="text-xs font-semibold text-charcoal-500 uppercase tracking-wider mb-1 block">Service Type</label>
|
| 73 |
+
<input value={contractType} onChange={e=>setContractType(e.target.value)} className="wedding-input" />
|
| 74 |
+
</div>
|
| 75 |
+
<div>
|
| 76 |
+
<label className="text-xs font-semibold text-charcoal-500 uppercase tracking-wider mb-1 block">Amount (Rs)</label>
|
| 77 |
+
<input value={amount} onChange={e=>setAmount(e.target.value)} className="wedding-input" type="number" />
|
| 78 |
+
</div>
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
|
| 82 |
+
{/* Sections */}
|
| 83 |
+
{sections.map((sec,i) => (
|
| 84 |
+
<div key={sec.id} className="wedding-card p-6">
|
| 85 |
+
<div className="flex items-center gap-2 mb-4">
|
| 86 |
+
<span className="font-heading text-sm font-semibold text-gold-500">Section {i+1}</span>
|
| 87 |
+
<input value={sec.title} onChange={e=>{
|
| 88 |
+
const s=[...sections];s[i].title=e.target.value;setSections(s)
|
| 89 |
+
}} className="flex-1 text-lg font-heading font-semibold text-charcoal-700 bg-transparent border-none outline-none" />
|
| 90 |
+
<button className="text-rose-400 hover:text-rose-600 text-xs">Remove</button>
|
| 91 |
+
</div>
|
| 92 |
+
{!sec.isDeliverables ? (
|
| 93 |
+
<textarea value={sec.content} onChange={e=>{
|
| 94 |
+
const s=[...sections];s[i].content=e.target.value;setSections(s)
|
| 95 |
+
}} className="wedding-input h-24" placeholder="Enter contract text..." />
|
| 96 |
+
) : (
|
| 97 |
+
<div className="space-y-4">
|
| 98 |
+
{/* Deliverables */}
|
| 99 |
+
{deliverables.map((d,j) => (
|
| 100 |
+
<div key={d.id} className="p-4 bg-ivory-50 rounded-xl border border-ivory-200">
|
| 101 |
+
<div className="grid sm:grid-cols-2 gap-3">
|
| 102 |
+
<div className="sm:col-span-2">
|
| 103 |
+
<label className="text-xs font-semibold text-charcoal-400 uppercase tracking-wider mb-1 block">Description</label>
|
| 104 |
+
<input value={d.desc} onChange={e=>{
|
| 105 |
+
const dl=[...deliverables];dl[j].desc=e.target.value;setDeliverables(dl)
|
| 106 |
+
}} className="wedding-input text-sm" />
|
| 107 |
+
</div>
|
| 108 |
+
<div>
|
| 109 |
+
<label className="text-xs font-semibold text-charcoal-400 uppercase tracking-wider mb-1 block">Due Date</label>
|
| 110 |
+
<input value={d.due} onChange={e=>{
|
| 111 |
+
const dl=[...deliverables];dl[j].due=e.target.value;setDeliverables(dl)
|
| 112 |
+
}} className="wedding-input text-sm" />
|
| 113 |
+
</div>
|
| 114 |
+
<div>
|
| 115 |
+
<label className="text-xs font-semibold text-charcoal-400 uppercase tracking-wider mb-1 block">Quantity</label>
|
| 116 |
+
<input value={d.qty} onChange={e=>{
|
| 117 |
+
const dl=[...deliverables];dl[j].qty=parseInt(e.target.value)||0;setDeliverables(dl)
|
| 118 |
+
}} className="wedding-input text-sm" type="number" />
|
| 119 |
+
</div>
|
| 120 |
+
<div className="sm:col-span-2">
|
| 121 |
+
<label className="text-xs font-semibold text-charcoal-400 uppercase tracking-wider mb-1 block">Acceptance Criteria</label>
|
| 122 |
+
<input value={d.criteria} onChange={e=>{
|
| 123 |
+
const dl=[...deliverables];dl[j].criteria=e.target.value;setDeliverables(dl)
|
| 124 |
+
}} className="wedding-input text-sm" />
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
<button className="text-xs text-rose-400 hover:text-rose-600 mt-2">Remove deliverable</button>
|
| 128 |
+
</div>
|
| 129 |
+
))}
|
| 130 |
+
<button onClick={addDeliverable} className="wedding-btn-outline text-xs w-full">+ Add Deliverable</button>
|
| 131 |
+
</div>
|
| 132 |
+
)}
|
| 133 |
+
</div>
|
| 134 |
+
))}
|
| 135 |
+
<button onClick={addSection} className="wedding-btn-outline text-sm w-full">+ Add Section</button>
|
| 136 |
+
</div>
|
| 137 |
+
|
| 138 |
+
{/* Right: Templates panel */}
|
| 139 |
+
<div className="space-y-6">
|
| 140 |
+
<div className="wedding-card p-6 sticky top-24">
|
| 141 |
+
<h2 className="font-heading text-lg font-semibold text-charcoal-700 mb-4">Templates</h2>
|
| 142 |
+
<p className="text-xs text-charcoal-400 mb-4">
|
| 143 |
+
Save and reuse contract structures per service category. Templates require admin approval before going live.
|
| 144 |
+
</p>
|
| 145 |
+
<div className="space-y-3">
|
| 146 |
+
{templates.map(t=>(
|
| 147 |
+
<button key={t.id} onClick={()=>setSelectedTemplate(t.id)}
|
| 148 |
+
className={"w-full text-left p-4 rounded-xl border transition-colors "+(selectedTemplate===t.id?'border-gold-300 bg-gold-50':'border-ivory-200 hover:border-gold-200')}>
|
| 149 |
+
<p className="font-heading text-sm font-semibold text-charcoal-700">{t.name}</p>
|
| 150 |
+
<p className="text-xs text-charcoal-400 mt-1">{t.desc}</p>
|
| 151 |
+
</button>
|
| 152 |
+
))}
|
| 153 |
+
</div>
|
| 154 |
+
<div className="mt-4 pt-4 border-t border-ivory-200">
|
| 155 |
+
<button className="wedding-btn-gold w-full text-sm">💾 Save as Template</button>
|
| 156 |
+
</div>
|
| 157 |
+
</div>
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
) : (
|
| 161 |
+
/* Preview mode */
|
| 162 |
+
<div className="max-w-3xl mx-auto">
|
| 163 |
+
<div className="wedding-card p-10">
|
| 164 |
+
<div className="text-center mb-10 pb-10 border-b border-ivory-200">
|
| 165 |
+
<h1 className="font-heading text-3xl font-semibold text-charcoal-700 mb-2">{contractType}</h1>
|
| 166 |
+
<p className="text-sm text-charcoal-400">Between: Lens & Light Studio and {clientName}</p>
|
| 167 |
+
<p className="text-sm text-charcoal-400">Date: {new Date().toLocaleDateString('en-US',{month:'long',day:'numeric',year:'numeric'})}</p>
|
| 168 |
+
<p className="font-heading text-2xl font-semibold text-charcoal-700 mt-4">Rs. {parseInt(amount).toLocaleString()}</p>
|
| 169 |
+
</div>
|
| 170 |
+
<div className="space-y-8">
|
| 171 |
+
{sections.map((sec,i) => (
|
| 172 |
+
<div key={sec.id}>
|
| 173 |
+
<h3 className="font-heading text-lg font-semibold text-charcoal-700 mb-3">{i+1}. {sec.title}</h3>
|
| 174 |
+
{!sec.isDeliverables ? (
|
| 175 |
+
<p className="text-sm text-charcoal-500 leading-relaxed">{sec.content}</p>
|
| 176 |
+
) : (
|
| 177 |
+
<div className="space-y-3">
|
| 178 |
+
{deliverables.map(d=>(
|
| 179 |
+
<div key={d.id} className="p-3 bg-ivory-50 rounded-lg">
|
| 180 |
+
<p className="text-sm font-medium text-charcoal-700">{d.desc}</p>
|
| 181 |
+
<p className="text-xs text-charcoal-400 mt-1">Due: {d.due} · Qty: {d.qty} · Criteria: {d.criteria}</p>
|
| 182 |
+
</div>
|
| 183 |
+
))}
|
| 184 |
+
</div>
|
| 185 |
+
)}
|
| 186 |
+
</div>
|
| 187 |
+
))}
|
| 188 |
+
</div>
|
| 189 |
+
<div className="mt-10 pt-10 border-t border-ivory-200">
|
| 190 |
+
<p className="text-xs text-charcoal-400 text-center">
|
| 191 |
+
This contract is created using the Evermore platform. All versions are immutably stored with a full audit trail.
|
| 192 |
+
E-signatures are legally binding under the Electronic Transactions Act.
|
| 193 |
+
</p>
|
| 194 |
+
</div>
|
| 195 |
+
</div>
|
| 196 |
+
</div>
|
| 197 |
+
)}
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
)
|
| 201 |
+
}
|