Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 137 additions & 55 deletions packages/esign/demo/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import React, { useState, useRef } from 'react';
import { useState, useRef } from 'react';
import SuperDocESign, { textToImageDataUrl } from '@superdoc-dev/esign';
import type {
SubmitData,
SigningState,
FieldChange,
DownloadData,
SuperDocESignHandle,
} from '@superdoc-dev/esign';
import type { SubmitData, SigningState, FieldChange, DownloadData, SuperDocESignHandle } from '@superdoc-dev/esign';
import CustomSignature from './CustomSignature';
import 'superdoc/style.css';
import './App.css';

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '';

const documentSource =
'https://storage.googleapis.com/public_static_hosting/public_demo_docs/service_agreement_updated.docx';
'https://storage.googleapis.com/public_static_hosting/public_demo_docs/service_agreement_with_table.docx';

// Document field definitions with labels
interface DocumentFieldConfig {
id: string;
value: string | string[][];
type?: 'text' | 'table';
label?: string;
readOnly?: boolean;
}

const signerFieldsConfig = [
{
Expand All @@ -25,13 +28,13 @@ const signerFieldsConfig = [
component: CustomSignature,
},
{
id: 'terms',
id: '1',
type: 'checkbox' as const,
label: 'I accept the terms and conditions',
validation: { required: true },
},
{
id: 'email',
id: '2',
type: 'checkbox' as const,
label: 'Send me a copy of the agreement',
validation: { required: false },
Expand Down Expand Up @@ -76,36 +79,43 @@ const downloadBlob = async (response: Response, fileName: string) => {
};

// Document field definitions with labels
const documentFieldsConfig = [
const documentFieldsConfig: DocumentFieldConfig[] = [
{
id: '123456',
label: 'Date',
defaultValue: new Date().toLocaleDateString(),
value: new Date().toLocaleDateString(),
readOnly: true,
type: 'text' as const,
type: 'text',
},
{
id: '234567',
label: 'Full Name',
defaultValue: 'John Doe',
value: 'John Doe',
readOnly: false,
type: 'text' as const,
type: 'text',
},
{
id: '345678',
label: 'Company',
defaultValue: 'SuperDoc',
value: 'SuperDoc',
readOnly: false,
type: 'text' as const,
type: 'text',
},
{ id: '456789', label: 'Plan', defaultValue: 'Premium', readOnly: false, type: 'text' as const },
{ id: '567890', label: 'State', defaultValue: 'CA', readOnly: false, type: 'text' as const },
{ id: '456789', label: 'Plan', value: 'Premium', readOnly: false, type: 'text' as const },
{ id: '567890', label: 'State', value: 'CA', readOnly: false, type: 'text' as const },
{
id: '678901',
label: 'Address',
defaultValue: '123 Main St, Anytown, USA',
value: '123 Main St, Anytown, USA',
readOnly: false,
type: 'text',
},
{
id: '238312460',
label: 'User responsibilities',
value: [[' - Provide accurate and complete information']],
readOnly: false,
type: 'text' as const,
type: 'table',
},
];

Expand All @@ -121,13 +131,20 @@ export function App() {
const esignRef = useRef<SuperDocESignHandle>(null);

// State for document field values
const [documentFields, setDocumentFields] = useState<Record<string, string>>(() =>
Object.fromEntries(documentFieldsConfig.map((f) => [f.id, f.defaultValue])),
const [documentFields, setDocumentFields] = useState<Record<string, string | string[][]>>(() =>
Object.fromEntries(documentFieldsConfig.map((f) => [f.id, f.value])),
);

const updateDocumentField = (id: string, value: string) => {
const updateDocumentField = (id: string, value: string | string[][]) => {
const fieldConfig = documentFieldsConfig.find((f) => f.id === id);
setDocumentFields((prev) => ({ ...prev, [id]: value }));
esignRef.current?.updateFieldInDocument({ id, value });
esignRef.current?.updateFieldInDocument({ id, value, type: fieldConfig?.type });
};

// Helper to get table rows as 2D array (for table fields)
const getTableRows = (fieldId: string): string[][] => {
const value = documentFields[fieldId];
return Array.isArray(value) ? value : [];
};

const log = (msg: string) => {
Expand Down Expand Up @@ -237,17 +254,13 @@ export function App() {
<div style={{ maxWidth: '1200px', margin: '40px auto', padding: '20px' }}>
<header style={{ marginBottom: '40px', textAlign: 'center' }}>
<h1>
<a
href="https://www.npmjs.com/package/@superdoc-dev/esign"
target="_blank"
rel="noopener"
>
<a href='https://www.npmjs.com/package/@superdoc-dev/esign' target='_blank' rel='noopener'>
@superdoc-dev/esign
</a>
</h1>
<p style={{ color: '#666' }}>
React eSign component from{' '}
<a href="https://superdoc.dev" target="_blank" rel="noopener">
<a href='https://superdoc.dev' target='_blank' rel='noopener'>
SuperDoc
</a>
</p>
Expand Down Expand Up @@ -308,9 +321,9 @@ export function App() {
Use the document toolbar to download the current agreement at any time.
</p>

<div className="main-layout-container">
<div className='main-layout-container'>
{/* Main content */}
<div className="main-content-area">
<div className='main-content-area'>
<SuperDocESign
ref={esignRef}
eventId={eventId}
Expand All @@ -336,7 +349,7 @@ export function App() {
onDownload={handleDownload}
onStateChange={handleStateChange}
onFieldChange={handleFieldChange}
documentHeight="500px"
documentHeight='500px'
/>

{/* Event Log */}
Expand Down Expand Up @@ -372,9 +385,9 @@ export function App() {
</div>

{/* Right Sidebar - Document Fields */}
<div className="document-fields-sidebar">
<div className='document-fields-sidebar'>
<h3>Document Fields</h3>
<div className="document-fields-list">
<div className='document-fields-list'>
{documentFieldsConfig.map((field) => (
<div key={field.id}>
<label
Expand All @@ -386,25 +399,94 @@ export function App() {
marginBottom: '4px',
}}
>
{field.label}
{field.label} {field.type === 'table' && <span style={{ color: '#9ca3af' }}>(table)</span>}
</label>
<input
type="text"
value={documentFields[field.id]}
onChange={(e) => updateDocumentField(field.id, e.target.value)}
readOnly={field.readOnly}
style={{
width: '100%',
padding: '8px 10px',
fontSize: '14px',
border: '1px solid #d1d5db',
borderRadius: '6px',
background: field.readOnly ? '#f3f4f6' : 'white',
color: field.readOnly ? '#6b7280' : '#111827',
cursor: field.readOnly ? 'not-allowed' : 'text',
boxSizing: 'border-box',
}}
/>
{field.type === 'table' ? (
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
{getTableRows(field.id).map((row, rowIndex) => (
<div key={rowIndex} style={{ display: 'flex', gap: '4px' }}>
{row.map((cellValue, cellIndex) => (
<input
key={cellIndex}
type='text'
value={cellValue}
onChange={(e) => {
const rows = [...getTableRows(field.id)];
rows[rowIndex] = [...rows[rowIndex]];
rows[rowIndex][cellIndex] = e.target.value;
updateDocumentField(field.id, rows);
}}
style={{
flex: 1,
padding: '8px 10px',
fontSize: '14px',
border: '1px solid #d1d5db',
borderRadius: '6px',
boxSizing: 'border-box',
}}
/>
))}
<button
onClick={() => {
const rows = [...getTableRows(field.id)];
if (rows.length > 1) {
rows.splice(rowIndex, 1);
updateDocumentField(field.id, rows);
}
}}
style={{
padding: '4px 8px',
fontSize: '14px',
border: '1px solid #d1d5db',
borderRadius: '6px',
background: '#f9fafb',
cursor: 'pointer',
}}
>
-
</button>
</div>
))}
<button
onClick={() => {
const rows = [...getTableRows(field.id)];
// Add new row with same column count as first row
const colCount = rows[0]?.length || 1;
rows.push(Array(colCount).fill(''));
updateDocumentField(field.id, rows);
}}
style={{
padding: '6px 10px',
fontSize: '13px',
border: '1px solid #d1d5db',
borderRadius: '6px',
background: '#f9fafb',
cursor: 'pointer',
alignSelf: 'flex-start',
}}
>
+ Add row
</button>
</div>
) : (
<input
type='text'
value={documentFields[field.id] as string}
onChange={(e) => updateDocumentField(field.id, e.target.value)}
readOnly={field.readOnly}
style={{
width: '100%',
padding: '8px 10px',
fontSize: '14px',
border: '1px solid #d1d5db',
borderRadius: '6px',
background: field.readOnly ? '#f3f4f6' : 'white',
color: field.readOnly ? '#6b7280' : '#111827',
cursor: field.readOnly ? 'not-allowed' : 'text',
boxSizing: 'border-box',
}}
/>
)}
</div>
))}
</div>
Expand Down
20 changes: 10 additions & 10 deletions packages/esign/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,25 @@ import reactPlugin from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';

export default [
{ ignores: ['dist/', 'node_modules/'] },
{ ignores: ['dist/', '**/dist/', 'node_modules/', '**/node_modules/'] },
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ['**/*.{ts,tsx}'],
plugins: {
react: reactPlugin,
'react-hooks': reactHooksPlugin
'react-hooks': reactHooksPlugin,
},
settings: {
react: {
version: 'detect'
}
version: 'detect',
},
},
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn'
}
'react-hooks/exhaustive-deps': 'warn',
},
},
{
files: ['demo/server/**/*.js'],
Expand All @@ -40,8 +40,8 @@ export default [
setInterval: 'readonly',
clearInterval: 'readonly',
setImmediate: 'readonly',
global: 'readonly'
}
}
}
global: 'readonly',
},
},
},
];
Loading