diff --git a/API_UPLOAD_FEATURES.md b/API_UPLOAD_FEATURES.md new file mode 100644 index 00000000..6b318c16 --- /dev/null +++ b/API_UPLOAD_FEATURES.md @@ -0,0 +1,161 @@ +# API Upload Features + +This document describes the new API upload functionality added to the Accord Protocol Template Playground. + +## Overview + +The template playground now includes comprehensive support for uploading templates and creating agreements using the Accord Protocol API format as defined in the [OpenAPI specification](https://raw.githubusercontent.com/accordproject/apap/refs/heads/main/openapi.json). + +## New Components + +### 1. Template Metadata Editor + +**Location**: `src/editors/editorsContainer/TemplateMetadata.tsx` + +A form-based editor that allows users to configure all template metadata fields required by the API: + +- **Basic Properties**: + - Author + - Display Name + - Version + - Description + - License (dropdown with common licenses) + - Keywords (tag-based input) + +- **Runtime Metadata**: + - Runtime (TypeScript/JavaScript/ES2015) + - Template Type (Clause/Contract/Template) + - Cicero Version + +### 2. Template Logic Editor + +**Location**: `src/editors/editorsContainer/TemplateLogic.tsx` + +A large text area editor for writing TypeScript/JavaScript logic code with: +- Syntax highlighting +- Undo/Redo functionality +- Monospace font for code readability + +### 3. API Upload Component + +**Location**: `src/components/ApiUpload.tsx` + +A comprehensive upload interface that provides: + +- **API Configuration**: Configurable API base URL +- **Template Upload**: Upload template to API with validation +- **Agreement Creation**: Create agreements from uploaded templates +- **Template Download**: Download template as JSON file +- **Preview Modal**: View formatted template JSON before upload + +## API Integration + +### Template Format + +The system automatically formats templates according to the Accord Protocol API specification: + +```json +{ + "uri": "resource:org.accordproject.protocol@1.0.0.Template#template-name", + "author": "Author Name", + "displayName": "Template Display Name", + "version": "1.0.0", + "description": "Template description", + "license": "Apache-2", + "keywords": ["keyword1", "keyword2"], + "metadata": { + "$class": "org.accordproject.protocol@1.0.0.TemplateMetadata", + "runtime": "typescript", + "template": "clause", + "cicero": "0.25.x" + }, + "logo": null, + "templateModel": { + "$class": "org.accordproject.protocol@1.0.0.TemplateModel", + "typeName": "templatename", + "model": { + "$class": "org.accordproject.protocol@1.0.0.CtoModel", + "ctoFiles": [ + { + "contents": "// Concerto model content", + "filename": "model.cto" + } + ] + } + }, + "text": { + "$class": "org.accordproject.protocol@1.0.0.Text", + "templateText": "TemplateMark content" + }, + "logic": "// Template logic code", + "sampleRequest": null +} +``` + +### API Endpoints + +The system integrates with the following API endpoints: + +- `POST /templates` - Upload template +- `POST /agreements` - Create agreement from template +- `GET /templates` - List templates +- `GET /agreements` - List agreements + +## Usage + +### 1. Configure Template Metadata + +1. Open the "Template Metadata" panel +2. Fill in required fields (Author and Display Name are mandatory) +3. Configure runtime settings as needed + +### 2. Write Template Logic (Optional) + +1. Open the "Template Logic" panel +2. Write TypeScript/JavaScript code for template execution +3. Use undo/redo for code editing + +### 3. Upload to API + +1. Open the "API Upload" panel +2. Configure the API base URL +3. Click "Upload Template to API" +4. Review the generated JSON in the preview modal +5. Optionally create agreements from the uploaded template + +### 4. Download Template + +- Use the "Download Template JSON" button to save the template locally + +## State Management + +The new features are integrated into the existing Zustand store with: + +- `templateMetadata`: Template metadata state +- `templateLogic`: Template logic code +- `setTemplateMetadata()`: Update metadata +- `setTemplateLogic()`: Update logic code + +## Data Persistence + +Template metadata and logic are included in: +- Shareable links (compressed data) +- Sample loading +- State restoration + +## Error Handling + +The system includes comprehensive error handling: +- Validation of required fields +- API error responses +- Network connectivity issues +- JSON parsing errors + +## Future Enhancements + +Potential improvements: +- Template versioning +- Batch upload capabilities +- Template marketplace integration +- Advanced validation rules +- Template testing framework \ No newline at end of file diff --git a/README.md b/README.md index 0cbfda66..162431bf 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,30 @@ The Accord Project Playground is an open-source project, welcoming contributions The Template Playground is deployed at: [https://playground.accordproject.org](https://playground.accordproject.org) +## API Configuration + +The playground can connect to an Accord Protocol API server for template and agreement management. To configure the API server URL: + +### Environment Variable Configuration + +Create a `.env` file in the project root with: + +```bash +# Accord Protocol API Server URL +VITE_API_SERVER_URL=http://your-accord-server:port +``` + +### Default Configuration + +If no environment variable is set, the playground defaults to: +``` +http://ec2-3-80-94-40.compute-1.amazonaws.com:9000 +``` + +### Development Proxy + +In development mode, the playground automatically proxies `/api/*` requests to the configured server to avoid CORS issues. + ---

diff --git a/package-lock.json b/package-lock.json index 72ba3214..40f0bd80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@types/styled-components": "^5.1.34", "antd": "^5.7.2", "core-js": "^3.37.1", + "for-each": "^0.3.5", "highlight.js": "^11.10.0", "immer": "^10.1.1", "jest-canvas-mock": "^2.5.2", @@ -12274,11 +12275,18 @@ } }, "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", "dependencies": { - "is-callable": "^1.1.3" + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/for-in": { diff --git a/package.json b/package.json index 5254fa79..8b6ceb4d 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@types/styled-components": "^5.1.34", "antd": "^5.7.2", "core-js": "^3.37.1", + "for-each": "^0.3.5", "highlight.js": "^11.10.0", "immer": "^10.1.1", "jest-canvas-mock": "^2.5.2", diff --git a/src/App.tsx b/src/App.tsx index 2b8f93e0..12d77953 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,6 +11,9 @@ import AgreementHtml from "./AgreementHtml"; import Errors from "./utils/helpers/Errors"; import TemplateMarkdown from "./editors/editorsContainer/TemplateMarkdown"; import TemplateModel from "./editors/editorsContainer/TemplateModel"; +import TemplateMetadata from "./editors/editorsContainer/TemplateMetadata"; +import TemplateLogic from "./editors/editorsContainer/TemplateLogic"; +import ApiUpload from "./components/ApiUpload"; import useAppStore from "./store/store"; import SampleDropdown from "./components/SampleDropdown"; import UseShare from "./components/UseShare"; @@ -115,6 +118,21 @@ const App = () => { label: "Concerto Model", children: , }, + { + key: "metadata", + label: "Template Metadata", + children: , + }, + { + key: "logic", + label: "Template Logic", + children: , + }, + { + key: "api", + label: "API Upload", + children: , + }, { key: "data", label: "Preview Data", diff --git a/src/components/ApiUpload.tsx b/src/components/ApiUpload.tsx new file mode 100644 index 00000000..d0147200 --- /dev/null +++ b/src/components/ApiUpload.tsx @@ -0,0 +1,175 @@ +import { useState } from 'react'; +import { Button, Input, Modal, message, Space, Card, Typography } from 'antd'; +import { UploadOutlined, CloudUploadOutlined, FileTextOutlined } from '@ant-design/icons'; +import { formatTemplateForApi, uploadTemplateToApi, createAgreementFromTemplate } from '../utils/templateApi'; +import useAppStore from '../store/store'; + +const { TextArea } = Input; +const { Text } = Typography; + +function ApiUpload() { + const textColor = useAppStore((state) => state.textColor); + const backgroundColor = useAppStore((state) => state.backgroundColor); + const templateMetadata = useAppStore((state) => state.templateMetadata); + const data = useAppStore((state) => state.data); + + const [apiUrl, setApiUrl] = useState('http://localhost:5175/api'); + const [isModalVisible, setIsModalVisible] = useState(false); + const [isUploading, setIsUploading] = useState(false); + const [uploadedTemplateId, setUploadedTemplateId] = useState(null); + const [templateJson, setTemplateJson] = useState(''); + + const handleUploadTemplate = async () => { + if (!templateMetadata.displayName || !templateMetadata.author) { + message.error('Please fill in the template metadata (Display Name and Author are required)'); + return; + } + + setIsUploading(true); + try { + const template = formatTemplateForApi(); + setTemplateJson(JSON.stringify(template, null, 2)); + + const response = await uploadTemplateToApi(apiUrl, template); + + if (response.ok) { + const result = await response.json(); + setUploadedTemplateId(result.uri || result.id); + message.success('Template uploaded successfully!'); + setIsModalVisible(true); + } else { + const errorText = await response.text(); + message.error(`Upload failed: ${response.status} - ${errorText}`); + } + } catch (error) { + message.error(`Upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`); + } finally { + setIsUploading(false); + } + }; + + const handleCreateAgreement = async () => { + if (!uploadedTemplateId) { + message.error('No template uploaded yet'); + return; + } + + setIsUploading(true); + try { + const agreementData = JSON.parse(data); + const response = await createAgreementFromTemplate(apiUrl, uploadedTemplateId, agreementData); + + if (response.ok) { + const result = await response.json(); + message.success('Agreement created successfully!'); + console.log('Created agreement:', result); + } else { + const errorText = await response.text(); + message.error(`Agreement creation failed: ${response.status} - ${errorText}`); + } + } catch (error) { + message.error(`Agreement creation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); + } finally { + setIsUploading(false); + } + }; + + const handleDownloadTemplate = () => { + const template = formatTemplateForApi(); + const blob = new Blob([JSON.stringify(template, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${templateMetadata.displayName || 'template'}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + return ( +

+ + + API Upload + + } + style={{ backgroundColor, borderColor: '#d9d9d9' }} + headStyle={{ color: textColor, backgroundColor }} + bodyStyle={{ color: textColor, backgroundColor }} + > +
+ API Base URL: + ) => setApiUrl(e.target.value)} + placeholder="http://localhost:3000" + style={{ marginTop: '8px', backgroundColor, color: textColor, borderColor: '#d9d9d9' }} + /> +
+ + + + + + + {uploadedTemplateId && ( + + )} + + + {uploadedTemplateId && ( +
+ + Template uploaded successfully! ID: {uploadedTemplateId} + +
+ )} +
+ + setIsModalVisible(false)} + footer={[ + + ]} + width={800} + > +