summaryrefslogtreecommitdiff
path: root/frontend/src/components/templates
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-24 15:50:43 +0000
committersoryu <soryu@soryu.co>2026-01-24 16:17:59 +0000
commit595548db950eca303a7d73ca09f31895d291534f (patch)
treead6317979526e6a683fa6987bc9979ec3ac055f3 /frontend/src/components/templates
parentabc5fbed331ea527ccaac0cd4120c4a0650f8bc0 (diff)
downloadsoryu-595548db950eca303a7d73ca09f31895d291534f.tar.gz
soryu-595548db950eca303a7d73ca09f31895d291534f.zip
feat(frontend): Add Templates page for managing contract templates
- Add NavStrip component for top navigation between pages - Create Templates page with template card grid layout - Create TemplateEditor component for editing phase deliverables - Add navigation state management in stores - Implement three built-in templates: - Simple: Plan phase (Plan deliverable), Execute phase (PR deliverable) - Specification: Research, Specify, Plan, Execute, Review phases with deliverables - Execute: Single execute phase with no deliverables - Support for creating custom templates with configurable phases/deliverables - Templates are persisted to localStorage - Add comprehensive CSS styling matching existing design patterns Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> [WIP] Heartbeat checkpoint - 2026-01-24 16:13:00 UTC [WIP] Heartbeat checkpoint - 2026-01-24 16:14:29 UTC
Diffstat (limited to 'frontend/src/components/templates')
-rw-r--r--frontend/src/components/templates/TemplateEditor.tsx185
1 files changed, 185 insertions, 0 deletions
diff --git a/frontend/src/components/templates/TemplateEditor.tsx b/frontend/src/components/templates/TemplateEditor.tsx
new file mode 100644
index 0000000..612cac0
--- /dev/null
+++ b/frontend/src/components/templates/TemplateEditor.tsx
@@ -0,0 +1,185 @@
+import React, { useState } from 'react'
+import { ContractTemplate, Phase, Deliverable } from '../../types'
+
+type Props = {
+ template: ContractTemplate
+ onSave: (template: ContractTemplate) => void
+ onCancel: () => void
+}
+
+export const TemplateEditor: React.FC<Props> = ({ template, onSave, onCancel }) => {
+ const [editedTemplate, setEditedTemplate] = useState<ContractTemplate>({ ...template, phases: template.phases.map(p => ({ ...p, deliverables: [...p.deliverables] })) })
+ const [newDeliverableName, setNewDeliverableName] = useState<{ [phaseId: string]: string }>({})
+
+ const handlePhaseNameChange = (phaseId: string, newName: string) => {
+ setEditedTemplate(prev => ({
+ ...prev,
+ phases: prev.phases.map(p =>
+ p.id === phaseId ? { ...p, name: newName } : p
+ )
+ }))
+ }
+
+ const handleDeliverableNameChange = (phaseId: string, deliverableId: string, newName: string) => {
+ setEditedTemplate(prev => ({
+ ...prev,
+ phases: prev.phases.map(p =>
+ p.id === phaseId
+ ? {
+ ...p,
+ deliverables: p.deliverables.map(d =>
+ d.id === deliverableId ? { ...d, name: newName } : d
+ )
+ }
+ : p
+ )
+ }))
+ }
+
+ const handleAddDeliverable = (phaseId: string) => {
+ const name = newDeliverableName[phaseId]?.trim()
+ if (!name) return
+
+ const newDeliverable: Deliverable = {
+ id: `deliverable-${Date.now()}`,
+ name
+ }
+
+ setEditedTemplate(prev => ({
+ ...prev,
+ phases: prev.phases.map(p =>
+ p.id === phaseId
+ ? { ...p, deliverables: [...p.deliverables, newDeliverable] }
+ : p
+ )
+ }))
+ setNewDeliverableName(prev => ({ ...prev, [phaseId]: '' }))
+ }
+
+ const handleRemoveDeliverable = (phaseId: string, deliverableId: string) => {
+ setEditedTemplate(prev => ({
+ ...prev,
+ phases: prev.phases.map(p =>
+ p.id === phaseId
+ ? { ...p, deliverables: p.deliverables.filter(d => d.id !== deliverableId) }
+ : p
+ )
+ }))
+ }
+
+ const handleAddPhase = () => {
+ const newPhase: Phase = {
+ id: `phase-${Date.now()}`,
+ name: 'New Phase',
+ deliverables: []
+ }
+ setEditedTemplate(prev => ({
+ ...prev,
+ phases: [...prev.phases, newPhase]
+ }))
+ }
+
+ const handleRemovePhase = (phaseId: string) => {
+ setEditedTemplate(prev => ({
+ ...prev,
+ phases: prev.phases.filter(p => p.id !== phaseId)
+ }))
+ }
+
+ return (
+ <div className="template-editor">
+ <div className="template-editor-header">
+ <h2 className="template-editor-title">Edit Template: {template.name}</h2>
+ <p className="template-editor-description">{template.description}</p>
+ </div>
+
+ <div className="template-editor-content">
+ <div className="phases-list">
+ {editedTemplate.phases.map((phase, phaseIndex) => (
+ <div key={phase.id} className="phase-card">
+ <div className="phase-header">
+ <span className="phase-number">{phaseIndex + 1}</span>
+ <input
+ type="text"
+ className="phase-name-input"
+ value={phase.name}
+ onChange={(e) => handlePhaseNameChange(phase.id, e.target.value)}
+ placeholder="Phase name"
+ />
+ {!template.isBuiltIn && (
+ <button
+ className="phase-remove-btn"
+ onClick={() => handleRemovePhase(phase.id)}
+ title="Remove phase"
+ >
+ x
+ </button>
+ )}
+ </div>
+
+ <div className="deliverables-list">
+ {phase.deliverables.length === 0 ? (
+ <div className="no-deliverables">No deliverables</div>
+ ) : (
+ phase.deliverables.map(deliverable => (
+ <div key={deliverable.id} className="deliverable-item">
+ <input
+ type="text"
+ className="deliverable-name-input"
+ value={deliverable.name}
+ onChange={(e) => handleDeliverableNameChange(phase.id, deliverable.id, e.target.value)}
+ />
+ <button
+ className="deliverable-remove-btn"
+ onClick={() => handleRemoveDeliverable(phase.id, deliverable.id)}
+ title="Remove deliverable"
+ >
+ x
+ </button>
+ </div>
+ ))
+ )}
+
+ <div className="add-deliverable-row">
+ <input
+ type="text"
+ className="add-deliverable-input"
+ placeholder="New deliverable name..."
+ value={newDeliverableName[phase.id] || ''}
+ onChange={(e) => setNewDeliverableName(prev => ({ ...prev, [phase.id]: e.target.value }))}
+ onKeyPress={(e) => {
+ if (e.key === 'Enter') {
+ handleAddDeliverable(phase.id)
+ }
+ }}
+ />
+ <button
+ className="add-deliverable-btn"
+ onClick={() => handleAddDeliverable(phase.id)}
+ >
+ + Add
+ </button>
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+
+ {!template.isBuiltIn && (
+ <button className="add-phase-btn" onClick={handleAddPhase}>
+ + Add Phase
+ </button>
+ )}
+ </div>
+
+ <div className="template-editor-footer">
+ <button className="modal-btn secondary" onClick={onCancel}>
+ Cancel
+ </button>
+ <button className="modal-btn primary" onClick={() => onSave(editedTemplate)}>
+ Save Changes
+ </button>
+ </div>
+ </div>
+ )
+}