feat(i18n): Add time blocks translations and fix deployment

- Add comprehensive timeBlocks translations (ES, FR, DE, EN)
- Add myAvailability translations (ES, FR, DE, EN)
- Add full helpTimeBlocks guide content (ES, FR, DE, EN)
- Add contracts guide translations (ES)
- Fix DATABASE_URL env var in deploy.sh for seed_platform_plugins
- Update Contracts page and HelpContracts guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-04 17:43:03 -05:00
parent 2d7c1dcd27
commit 29e99631c9
7 changed files with 1938 additions and 116 deletions

View File

@@ -327,7 +327,7 @@ const Contracts: React.FC = () => {
};
return (
<span className={`px-2 py-1 rounded-full text-xs font-medium ${styles[scope]}`}>
{scope === 'CUSTOMER' ? 'Customer-Level' : 'Per Appointment'}
{t(`contracts.scope.${scope.toLowerCase()}`)}
</span>
);
};
@@ -385,7 +385,7 @@ const Contracts: React.FC = () => {
className="px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2"
>
<Plus size={16} />
New Template
{t('contracts.newTemplate')}
</button>
</button>
@@ -399,7 +399,7 @@ const Contracts: React.FC = () => {
type="text"
value={templatesSearch}
onChange={(e) => setTemplatesSearch(e.target.value)}
placeholder="Search templates..."
placeholder={t('contracts.searchTemplates')}
className="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white text-sm"
/>
</div>
@@ -414,7 +414,7 @@ const Contracts: React.FC = () => {
: 'text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700'
}`}
>
{tab === 'all' ? 'All' : tab.charAt(0) + tab.slice(1).toLowerCase()}
{tab === 'all' ? t('contracts.all') : t(`contracts.status.${tab.toLowerCase()}`)}
<span className="ml-1 text-xs">({templateStatusCounts[tab]})</span>
</button>
))}
@@ -425,18 +425,18 @@ const Contracts: React.FC = () => {
{filteredTemplates.length === 0 ? (
<div className="py-8 text-center text-gray-500 dark:text-gray-400">
<FileSignature className="w-12 h-12 mx-auto mb-3 text-gray-300 dark:text-gray-600" />
<p>{templatesSearch ? 'No templates found' : 'No templates yet. Create your first template to get started.'}</p>
<p>{templatesSearch ? t('contracts.noTemplatesSearch') : t('contracts.noTemplatesEmpty')}</p>
</div>
) : (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead>
<tr>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Template</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Scope</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Status</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Version</th>
<th className="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Actions</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">{t('contracts.table.template')}</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">{t('contracts.table.scope')}</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">{t('contracts.table.status')}</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">{t('contracts.table.version')}</th>
<th className="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">{t('contracts.table.actions')}</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
@@ -461,25 +461,25 @@ const Contracts: React.FC = () => {
window.open(URL.createObjectURL(blob), '_blank');
} catch (error) {
console.error('Failed to load PDF preview:', error);
alert('Failed to load PDF preview.');
alert(t('contracts.actions.previewFailed'));
}
}}
className="p-1.5 text-gray-500 hover:text-blue-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors"
title="Preview PDF"
title={t('contracts.actions.preview')}
>
<Eye size={16} />
</button>
<button
onClick={() => openEditTemplateModal(template)}
className="p-1.5 text-gray-500 hover:text-blue-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors"
title="Edit"
title={t('contracts.actions.edit')}
>
<Pencil size={16} />
</button>
<button
onClick={() => setDeleteConfirmId(template.id)}
className="p-1.5 text-gray-500 hover:text-red-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors"
title="Delete"
title={t('contracts.actions.delete')}
>
<Trash2 size={16} />
</button>
@@ -504,7 +504,7 @@ const Contracts: React.FC = () => {
<div className="flex items-center gap-3">
{contractsExpanded ? <ChevronDown size={20} /> : <ChevronRight size={20} />}
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Sent Contracts
{t('contracts.sentContracts')}
</h2>
<span className="px-2 py-0.5 rounded-full bg-gray-100 dark:bg-gray-700 text-sm">
{contracts?.length || 0}
@@ -519,7 +519,7 @@ const Contracts: React.FC = () => {
className="px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
<Plus size={16} />
Send Contract
{t('contracts.actions.send')}
</button>
</button>
@@ -533,7 +533,7 @@ const Contracts: React.FC = () => {
type="text"
value={contractsSearch}
onChange={(e) => setContractsSearch(e.target.value)}
placeholder="Search contracts..."
placeholder={t('contracts.searchContracts')}
className="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white text-sm"
/>
</div>
@@ -548,7 +548,7 @@ const Contracts: React.FC = () => {
: 'text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700'
}`}
>
{tab === 'all' ? 'All' : tab.charAt(0) + tab.slice(1).toLowerCase()}
{tab === 'all' ? t('contracts.all') : t(`contracts.status.${tab.toLowerCase()}`)}
<span className="ml-1 text-xs">({contractStatusCounts[tab]})</span>
</button>
))}
@@ -559,18 +559,18 @@ const Contracts: React.FC = () => {
{filteredContracts.length === 0 ? (
<div className="py-8 text-center text-gray-500 dark:text-gray-400">
<FileSignature className="w-12 h-12 mx-auto mb-3 text-gray-300 dark:text-gray-600" />
<p>{contractsSearch ? 'No contracts found' : 'No contracts sent yet.'}</p>
<p>{contractsSearch ? t('contracts.noContractsSearch') : t('contracts.noContractsEmpty')}</p>
</div>
) : (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead>
<tr>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Customer</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Contract</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Status</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Created</th>
<th className="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Actions</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">{t('contracts.table.customer')}</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">{t('contracts.table.contract')}</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">{t('contracts.table.status')}</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">{t('contracts.table.created')}</th>
<th className="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">{t('contracts.table.actions')}</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
@@ -583,42 +583,42 @@ const Contracts: React.FC = () => {
<td className="px-4 py-3">
<div className="font-medium text-gray-900 dark:text-white">{contract.template_name}</div>
{contract.sent_at && (
<div className="text-xs text-gray-500 dark:text-gray-400">Sent: {formatDate(contract.sent_at)}</div>
<div className="text-xs text-gray-500 dark:text-gray-400">{t('contracts.sentAt')}: {formatDate(contract.sent_at)}</div>
)}
</td>
<td className="px-4 py-3">
{getContractStatusBadge(contract.status)}
{contract.signed_at && (
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">Signed: {formatDate(contract.signed_at)}</div>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">{t('contracts.signedAt')}: {formatDate(contract.signed_at)}</div>
)}
</td>
<td className="px-4 py-3 text-sm text-gray-500 dark:text-gray-400">{formatDate(contract.created_at)}</td>
<td className="px-4 py-3 text-right">
<div className="flex items-center justify-end gap-1">
<button onClick={() => setViewingContract(contract)} className="p-1.5 text-gray-500 hover:text-blue-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors" title="View Details">
<button onClick={() => setViewingContract(contract)} className="p-1.5 text-gray-500 hover:text-blue-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors" title={t('contracts.actions.viewDetails')}>
<Eye size={16} />
</button>
{contract.status === 'PENDING' && (
<>
<button onClick={() => copySigningLink(contract)} className="p-1.5 text-gray-500 hover:text-blue-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors" title="Copy Signing Link">
<button onClick={() => copySigningLink(contract)} className="p-1.5 text-gray-500 hover:text-blue-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors" title={t('contracts.actions.copyLink')}>
<Copy size={16} />
</button>
{!contract.sent_at ? (
<button onClick={() => handleSendContract(contract.id)} disabled={sendContract.isPending} className="p-1.5 text-gray-500 hover:text-green-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors" title="Send Email">
<button onClick={() => handleSendContract(contract.id)} disabled={sendContract.isPending} className="p-1.5 text-gray-500 hover:text-green-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors" title={t('contracts.actions.sendEmail')}>
<Send size={16} />
</button>
) : (
<button onClick={() => handleResendContract(contract.id)} disabled={resendContract.isPending} className="p-1.5 text-gray-500 hover:text-green-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors" title="Resend Email">
<button onClick={() => handleResendContract(contract.id)} disabled={resendContract.isPending} className="p-1.5 text-gray-500 hover:text-green-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors" title={t('contracts.actions.resend')}>
<RefreshCw size={16} />
</button>
)}
<button onClick={() => setVoidingContract(contract)} className="p-1.5 text-gray-500 hover:text-red-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors" title="Void Contract">
<button onClick={() => setVoidingContract(contract)} className="p-1.5 text-gray-500 hover:text-red-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors" title={t('contracts.actions.void')}>
<Ban size={16} />
</button>
</>
)}
{contract.public_token && contract.status === 'PENDING' && (
<a href={`/sign/${contract.public_token}`} target="_blank" rel="noopener noreferrer" className="p-1.5 text-gray-500 hover:text-blue-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors" title="Open Signing Page">
<a href={`/sign/${contract.public_token}`} target="_blank" rel="noopener noreferrer" className="p-1.5 text-gray-500 hover:text-blue-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors" title={t('contracts.actions.openSigningPage')}>
<ExternalLink size={16} />
</a>
)}
@@ -639,23 +639,23 @@ const Contracts: React.FC = () => {
<div className="fixed inset-0 z-50 bg-black/50 flex items-center justify-center p-4" onClick={() => setIsCreateContractModalOpen(false)}>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-lg w-full max-h-[90vh] overflow-y-auto" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">Send Contract</h2>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">{t('contracts.sendContract.title')}</h2>
<button onClick={() => setIsCreateContractModalOpen(false)} className="p-2 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
<X size={20} />
</button>
</div>
<form onSubmit={handleCreateContract} className="p-6 space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Contract Template *</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{t('contracts.sendContract.selectTemplate')} *</label>
<select value={selectedTemplateId} onChange={(e) => setSelectedTemplateId(e.target.value)} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white" required>
<option value="">Select a template...</option>
<option value="">{t('contracts.sendContract.selectTemplatePlaceholder')}</option>
{activeTemplates?.map((template) => (
<option key={template.id} value={template.id}>{template.name} (v{template.version})</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Customer *</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{t('contracts.sendContract.selectCustomer')} *</label>
<div className="relative" ref={customerDropdownRef}>
{selectedCustomerId ? (
<div className="flex items-center gap-2 w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-gray-50 dark:bg-gray-700">
@@ -669,7 +669,7 @@ const Contracts: React.FC = () => {
<>
<div className="relative">
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
<input type="text" value={customerSearchTerm} onChange={(e) => { setCustomerSearchTerm(e.target.value); setIsCustomerDropdownOpen(true); }} onFocus={() => setIsCustomerDropdownOpen(true)} placeholder="Search customers..." className="w-full pl-10 pr-10 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white" />
<input type="text" value={customerSearchTerm} onChange={(e) => { setCustomerSearchTerm(e.target.value); setIsCustomerDropdownOpen(true); }} onFocus={() => setIsCustomerDropdownOpen(true)} placeholder={t('contracts.sendContract.searchCustomers')} className="w-full pl-10 pr-10 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white" />
<ChevronDown size={16} className={`absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 transition-transform ${isCustomerDropdownOpen ? 'rotate-180' : ''}`} />
</div>
{isCustomerDropdownOpen && (
@@ -677,15 +677,15 @@ const Contracts: React.FC = () => {
{customersLoading ? (
<div className="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 flex items-center gap-2">
<Loader2 size={14} className="animate-spin" />
Loading customers...
{t('contracts.sendContract.loadingCustomers')}
</div>
) : customersError ? (
<div className="px-4 py-3 text-sm text-red-500 dark:text-red-400">
Failed to load customers
{t('contracts.sendContract.loadCustomersFailed')}
</div>
) : filteredCustomers.length === 0 ? (
<div className="px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
{customers.length === 0 ? 'No customers available. Create customers first.' : 'No matching customers'}
{customers.length === 0 ? t('contracts.sendContract.noCustomers') : t('contracts.sendContract.noMatchingCustomers')}
</div>
) : (
filteredCustomers.map((customer) => (
@@ -708,13 +708,13 @@ const Contracts: React.FC = () => {
</div>
<div className="flex items-center gap-3">
<input type="checkbox" id="sendEmail" checked={sendEmailOnCreate} onChange={(e) => setSendEmailOnCreate(e.target.checked)} className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" />
<label htmlFor="sendEmail" className="text-sm text-gray-700 dark:text-gray-300">Send signing request email immediately</label>
<label htmlFor="sendEmail" className="text-sm text-gray-700 dark:text-gray-300">{t('contracts.sendContract.sendImmediately')}</label>
</div>
<div className="flex justify-end gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
<button type="button" onClick={() => setIsCreateContractModalOpen(false)} className="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">Cancel</button>
<button type="button" onClick={() => setIsCreateContractModalOpen(false)} className="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">{t('common.cancel')}</button>
<button type="submit" disabled={createContract.isPending || !selectedTemplateId || !selectedCustomerId} className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center gap-2">
{createContract.isPending && <Loader2 className="w-4 h-4 animate-spin" />}
Send Contract
{t('contracts.sendContract.send')}
</button>
</div>
</form>
@@ -727,7 +727,7 @@ const Contracts: React.FC = () => {
<div className="fixed inset-0 z-50 bg-black/50 flex items-center justify-center p-4" onClick={() => setViewingContract(null)}>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-3xl w-full max-h-[90vh] overflow-y-auto" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">Contract Details</h2>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">{t('contracts.contractDetails.title')}</h2>
<button onClick={() => setViewingContract(null)} className="p-2 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
<X size={20} />
</button>
@@ -735,32 +735,32 @@ const Contracts: React.FC = () => {
<div className="p-6 space-y-6">
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-500 dark:text-gray-400">Customer</p>
<p className="text-sm text-gray-500 dark:text-gray-400">{t('contracts.contractDetails.customer')}</p>
<p className="font-medium text-gray-900 dark:text-white">{viewingContract.customer_name}</p>
<p className="text-sm text-gray-600 dark:text-gray-400">{viewingContract.customer_email}</p>
</div>
<div>
<p className="text-sm text-gray-500 dark:text-gray-400">Template</p>
<p className="text-sm text-gray-500 dark:text-gray-400">{t('contracts.contractDetails.template')}</p>
<p className="font-medium text-gray-900 dark:text-white">{viewingContract.template_name} (v{viewingContract.template_version})</p>
</div>
<div>
<p className="text-sm text-gray-500 dark:text-gray-400">Status</p>
<p className="text-sm text-gray-500 dark:text-gray-400">{t('contracts.contractDetails.status')}</p>
{getContractStatusBadge(viewingContract.status)}
</div>
<div>
<p className="text-sm text-gray-500 dark:text-gray-400">Created</p>
<p className="text-sm text-gray-500 dark:text-gray-400">{t('contracts.contractDetails.created')}</p>
<p className="text-gray-900 dark:text-white">{formatDate(viewingContract.created_at)}</p>
</div>
</div>
{viewingContract.content && (
<div>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-2">Content Preview</p>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-2">{t('contracts.contractDetails.contentPreview')}</p>
<div className="prose dark:prose-invert max-w-none p-4 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-900 max-h-64 overflow-y-auto" dangerouslySetInnerHTML={{ __html: viewingContract.content }} />
</div>
)}
{viewingContract.public_token && viewingContract.status === 'PENDING' && (
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<p className="text-sm font-medium text-blue-700 dark:text-blue-300 mb-2">Signing Link</p>
<p className="text-sm font-medium text-blue-700 dark:text-blue-300 mb-2">{t('contracts.contractDetails.signingLink')}</p>
<div className="flex items-center gap-2">
<input type="text" readOnly value={`${window.location.origin}/sign/${viewingContract.public_token}`} className="flex-1 px-3 py-2 text-sm bg-white dark:bg-gray-800 border border-blue-200 dark:border-blue-700 rounded" />
<button onClick={() => copySigningLink(viewingContract)} className="px-3 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
@@ -782,18 +782,18 @@ const Contracts: React.FC = () => {
<div className="w-10 h-10 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
<Ban className="w-5 h-5 text-red-600 dark:text-red-400" />
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Void Contract</h3>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{t('contracts.voidContract.title')}</h3>
</div>
<p className="text-gray-600 dark:text-gray-400 mb-4">Voiding this contract will cancel it. The customer will no longer be able to sign.</p>
<p className="text-gray-600 dark:text-gray-400 mb-4">{t('contracts.voidContract.description')}</p>
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Reason for voiding *</label>
<textarea value={voidReason} onChange={(e) => setVoidReason(e.target.value)} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent dark:bg-gray-700 dark:text-white" rows={3} placeholder="Enter the reason..." required />
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{t('contracts.voidContract.reason')} *</label>
<textarea value={voidReason} onChange={(e) => setVoidReason(e.target.value)} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent dark:bg-gray-700 dark:text-white" rows={3} placeholder={t('contracts.voidContract.reasonPlaceholder')} required />
</div>
<div className="flex justify-end gap-3">
<button onClick={() => { setVoidingContract(null); setVoidReason(''); }} className="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">Cancel</button>
<button onClick={() => { setVoidingContract(null); setVoidReason(''); }} className="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">{t('common.cancel')}</button>
<button onClick={handleVoidContract} disabled={voidContract.isPending || !voidReason.trim()} className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors disabled:opacity-50 flex items-center gap-2">
{voidContract.isPending && <Loader2 className="w-4 h-4 animate-spin" />}
Void Contract
{t('contracts.voidContract.confirm')}
</button>
</div>
</div>
@@ -805,7 +805,7 @@ const Contracts: React.FC = () => {
<div className="fixed inset-0 z-50 bg-black/50 flex items-center justify-center p-4" onClick={() => setIsTemplateModalOpen(false)}>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">{editingTemplate ? 'Edit Template' : 'Create Template'}</h2>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">{editingTemplate ? t('contracts.editTemplate') : t('contracts.createTemplate')}</h2>
<button onClick={() => setIsTemplateModalOpen(false)} className="p-2 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
<X size={20} />
</button>
@@ -813,37 +813,37 @@ const Contracts: React.FC = () => {
<form onSubmit={handleTemplateSubmit} className="p-6 space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Template Name *</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{t('contracts.templateName')} *</label>
<input type="text" value={templateFormData.name} onChange={(e) => setTemplateFormData({ ...templateFormData, name: e.target.value })} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white" required />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Scope *</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{t('contracts.scope.label')} *</label>
<select value={templateFormData.scope} onChange={(e) => setTemplateFormData({ ...templateFormData, scope: e.target.value as ContractScope })} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white">
<option value="APPOINTMENT">Per Appointment (e.g., liability waivers)</option>
<option value="CUSTOMER">Customer-Level (e.g., terms of service)</option>
<option value="APPOINTMENT">{t('contracts.scope.appointment')} ({t('contracts.scope.appointmentDesc')})</option>
<option value="CUSTOMER">{t('contracts.scope.customer')} ({t('contracts.scope.customerDesc')})</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Status</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{t('contracts.status.label')}</label>
<select value={templateFormData.status} onChange={(e) => setTemplateFormData({ ...templateFormData, status: e.target.value as ContractTemplateStatus })} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white">
<option value="DRAFT">Draft</option>
<option value="ACTIVE">Active</option>
<option value="ARCHIVED">Archived</option>
<option value="DRAFT">{t('contracts.status.draft')}</option>
<option value="ACTIVE">{t('contracts.status.active')}</option>
<option value="ARCHIVED">{t('contracts.status.archived')}</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Expires After (days)</label>
<input type="number" value={templateFormData.expires_after_days || ''} onChange={(e) => setTemplateFormData({ ...templateFormData, expires_after_days: e.target.value ? parseInt(e.target.value) : null })} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white" placeholder="Leave blank for no expiration" min="1" />
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{t('contracts.expiresAfterDays')}</label>
<input type="number" value={templateFormData.expires_after_days || ''} onChange={(e) => setTemplateFormData({ ...templateFormData, expires_after_days: e.target.value ? parseInt(e.target.value) : null })} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white" placeholder={t('contracts.expiresAfterDaysHint')} min="1" />
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Description</label>
<input type="text" value={templateFormData.description} onChange={(e) => setTemplateFormData({ ...templateFormData, description: e.target.value })} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white" placeholder="Brief description of this template" />
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{t('contracts.templateDescription')}</label>
<input type="text" value={templateFormData.description} onChange={(e) => setTemplateFormData({ ...templateFormData, description: e.target.value })} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white" placeholder={t('contracts.templateDescription')} />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Contract Content (HTML) *</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{t('contracts.contentHtml')} *</label>
<div className="mb-2 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<p className="text-sm text-blue-700 dark:text-blue-300 mb-2"><strong>Available Variables:</strong></p>
<p className="text-sm text-blue-700 dark:text-blue-300 mb-2"><strong>{t('contracts.availableVariables')}:</strong></p>
<div className="flex flex-wrap gap-2 text-xs font-mono">
{['{{CUSTOMER_NAME}}', '{{CUSTOMER_EMAIL}}', '{{BUSINESS_NAME}}', '{{DATE}}', '{{YEAR}}'].map((v) => (
<span key={v} className="px-2 py-1 bg-white dark:bg-gray-700 rounded cursor-pointer hover:bg-blue-100 dark:hover:bg-blue-800" onClick={() => setTemplateFormData({ ...templateFormData, content: templateFormData.content + v })}>{v}</span>
@@ -853,10 +853,10 @@ const Contracts: React.FC = () => {
<textarea value={templateFormData.content} onChange={(e) => setTemplateFormData({ ...templateFormData, content: e.target.value })} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white font-mono text-sm" rows={12} required placeholder="<h1>Service Agreement</h1>&#10;<p>This agreement is between {{BUSINESS_NAME}} and {{CUSTOMER_NAME}}...</p>" />
</div>
<div className="flex justify-end gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
<button type="button" onClick={() => setIsTemplateModalOpen(false)} className="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">Cancel</button>
<button type="button" onClick={() => setIsTemplateModalOpen(false)} className="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">{t('common.cancel')}</button>
<button type="submit" disabled={createTemplate.isPending || updateTemplate.isPending} className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center gap-2">
{(createTemplate.isPending || updateTemplate.isPending) && <Loader2 className="w-4 h-4 animate-spin" />}
{editingTemplate ? 'Save Changes' : 'Create Template'}
{editingTemplate ? t('contracts.actions.saveChanges') : t('contracts.createTemplate')}
</button>
</div>
</form>
@@ -872,14 +872,14 @@ const Contracts: React.FC = () => {
<div className="w-10 h-10 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
<AlertCircle className="w-5 h-5 text-red-600 dark:text-red-400" />
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Delete Template</h3>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{t('contracts.deleteTemplate.title')}</h3>
</div>
<p className="text-gray-600 dark:text-gray-400 mb-6">Are you sure you want to delete this template? This action cannot be undone.</p>
<p className="text-gray-600 dark:text-gray-400 mb-6">{t('contracts.deleteTemplate.description')}</p>
<div className="flex justify-end gap-3">
<button onClick={() => setDeleteConfirmId(null)} className="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">Cancel</button>
<button onClick={() => setDeleteConfirmId(null)} className="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">{t('common.cancel')}</button>
<button onClick={() => handleDeleteTemplate(deleteConfirmId)} disabled={deleteTemplate.isPending} className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors disabled:opacity-50 flex items-center gap-2">
{deleteTemplate.isPending && <Loader2 className="w-4 h-4 animate-spin" />}
Delete
{t('contracts.deleteTemplate.confirm')}
</button>
</div>
</div>

View File

@@ -1,21 +1,27 @@
/**
* Help Contracts Page
*
* Comprehensive help documentation for the Contracts management page.
*/
import React from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import {
ArrowLeft, FileSignature, FileText, Send, CheckCircle, ChevronRight,
HelpCircle, Shield, Clock, Users, AlertCircle, Copy, Eye, Download,
Plus, Pencil, Trash2, ChevronDown, RefreshCw, Ban, ExternalLink,
LayoutGrid, Search, Filter,
} from 'lucide-react';
const HelpContracts: React.FC = () => {
const navigate = useNavigate();
const { t } = useTranslation();
return (
<div className="max-w-4xl mx-auto py-8 px-4">
<button onClick={() => navigate(-1)} className="flex items-center gap-2 text-brand-600 hover:text-brand-700 dark:text-brand-400 mb-6">
<ArrowLeft size={20} /> Back
<ArrowLeft size={20} /> {t('common.back', 'Back')}
</button>
<div className="mb-8">
@@ -44,6 +50,42 @@ const HelpContracts: React.FC = () => {
</div>
</section>
<section className="mb-10">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
<LayoutGrid size={20} className="text-brand-500" /> Page Layout
</h2>
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<p className="text-gray-600 dark:text-gray-300 mb-4">
The Contracts page is organized into two collapsible sections for easy management:
</p>
<div className="space-y-4">
<div className="flex items-start gap-3 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<ChevronDown size={20} className="text-blue-500 mt-0.5" />
<div>
<h4 className="font-medium text-gray-900 dark:text-white">Templates Section</h4>
<p className="text-sm text-gray-500 dark:text-gray-400">
Create and manage reusable contract templates. Includes search, status filters (All, Active, Draft, Archived), and actions to create, edit, preview PDF, and delete templates.
</p>
</div>
</div>
<div className="flex items-start gap-3 p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
<ChevronDown size={20} className="text-green-500 mt-0.5" />
<div>
<h4 className="font-medium text-gray-900 dark:text-white">Sent Contracts Section</h4>
<p className="text-sm text-gray-500 dark:text-gray-400">
Track contracts sent to customers. Includes search, status filters (All, Pending, Signed, Expired, Voided), and actions to view, copy link, resend, or void contracts.
</p>
</div>
</div>
</div>
<div className="mt-4 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
<p className="text-sm text-gray-600 dark:text-gray-300">
<strong>Tip:</strong> Click the section header to collapse/expand each section. The count badge shows how many items are in each section.
</p>
</div>
</div>
</section>
<section className="mb-10">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
<FileText size={20} className="text-brand-500" /> Contract Templates
@@ -80,10 +122,10 @@ const HelpContracts: React.FC = () => {
<p className="text-sm text-gray-500 dark:text-gray-400">One-time contracts per customer (e.g., privacy policy, terms of service)</p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-green-50 dark:bg-green-900/20 rounded-lg">
<Clock size={20} className="text-green-500 mt-0.5" />
<div className="flex items-start gap-3 p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
<Clock size={20} className="text-purple-500 mt-0.5" />
<div>
<span className="font-medium text-gray-900 dark:text-white">Appointment-Level</span>
<span className="font-medium text-gray-900 dark:text-white">Per Appointment</span>
<p className="text-sm text-gray-500 dark:text-gray-400">Signed for each booking (e.g., liability waivers, service agreements)</p>
</div>
</div>
@@ -91,11 +133,114 @@ const HelpContracts: React.FC = () => {
</div>
</section>
<section className="mb-10">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
<Plus size={20} className="text-brand-500" /> Creating Templates
</h2>
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<p className="text-gray-600 dark:text-gray-300 mb-4">
Click the <strong>"New Template"</strong> button in the Templates section to create a new contract template:
</p>
<ol className="space-y-4">
<li className="flex items-start gap-3">
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-600 text-white text-sm flex items-center justify-center">1</span>
<div>
<h4 className="font-medium text-gray-900 dark:text-white">Enter Template Name</h4>
<p className="text-sm text-gray-500 dark:text-gray-400">
Give your template a clear, descriptive name (e.g., "Service Agreement", "Liability Waiver").
</p>
</div>
</li>
<li className="flex items-start gap-3">
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-600 text-white text-sm flex items-center justify-center">2</span>
<div>
<h4 className="font-medium text-gray-900 dark:text-white">Select Scope</h4>
<p className="text-sm text-gray-500 dark:text-gray-400">
Choose "Per Appointment" for waivers or "Customer-Level" for one-time agreements.
</p>
</div>
</li>
<li className="flex items-start gap-3">
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-600 text-white text-sm flex items-center justify-center">3</span>
<div>
<h4 className="font-medium text-gray-900 dark:text-white">Set Status</h4>
<p className="text-sm text-gray-500 dark:text-gray-400">
Start as "Draft" while editing. Change to "Active" when ready to send to customers.
</p>
</div>
</li>
<li className="flex items-start gap-3">
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-600 text-white text-sm flex items-center justify-center">4</span>
<div>
<h4 className="font-medium text-gray-900 dark:text-white">Set Expiration (Optional)</h4>
<p className="text-sm text-gray-500 dark:text-gray-400">
Enter days until contracts expire. Leave blank for no expiration.
</p>
</div>
</li>
<li className="flex items-start gap-3">
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-600 text-white text-sm flex items-center justify-center">5</span>
<div>
<h4 className="font-medium text-gray-900 dark:text-white">Write Contract Content</h4>
<p className="text-sm text-gray-500 dark:text-gray-400">
Enter your contract text using HTML formatting. Click variable chips to insert placeholders.
</p>
</div>
</li>
</ol>
</div>
</section>
<section className="mb-10">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
<Pencil size={20} className="text-brand-500" /> Managing Templates
</h2>
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<p className="text-gray-600 dark:text-gray-300 mb-4">
Each template in the list has action buttons on the right:
</p>
<div className="space-y-3">
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
<Eye size={18} className="text-blue-500" />
<div>
<span className="font-medium text-gray-900 dark:text-white">Preview PDF</span>
<span className="text-sm text-gray-500 dark:text-gray-400 ml-2">- See how the contract looks as a PDF with sample data</span>
</div>
</div>
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
<Pencil size={18} className="text-blue-500" />
<div>
<span className="font-medium text-gray-900 dark:text-white">Edit</span>
<span className="text-sm text-gray-500 dark:text-gray-400 ml-2">- Modify template name, content, scope, or status</span>
</div>
</div>
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
<Trash2 size={18} className="text-red-500" />
<div>
<span className="font-medium text-gray-900 dark:text-white">Delete</span>
<span className="text-sm text-gray-500 dark:text-gray-400 ml-2">- Remove the template (requires confirmation)</span>
</div>
</div>
</div>
<div className="mt-4 p-4 bg-amber-50 dark:bg-amber-900/20 rounded-lg border border-amber-200 dark:border-amber-800">
<div className="flex items-start gap-2">
<AlertCircle size={16} className="text-amber-600 dark:text-amber-400 mt-0.5" />
<p className="text-sm text-amber-800 dark:text-amber-200">
<strong>Note:</strong> Only "Active" templates can be used to send contracts. Switch templates to Active status when they're ready for use.
</p>
</div>
</div>
</div>
</section>
<section className="mb-10">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
<Send size={20} className="text-brand-500" /> Sending Contracts
</h2>
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<p className="text-gray-600 dark:text-gray-300 mb-4">
Click the <strong>"Send Contract"</strong> button in the Sent Contracts section:
</p>
<div className="space-y-4">
<div className="flex items-start gap-3">
<div className="w-6 h-6 rounded-full bg-brand-100 dark:bg-brand-900/30 flex items-center justify-center flex-shrink-0">
@@ -112,7 +257,7 @@ const HelpContracts: React.FC = () => {
</div>
<div>
<h4 className="font-medium text-gray-900 dark:text-white">Choose a Customer</h4>
<p className="text-sm text-gray-500 dark:text-gray-400">Variables are automatically filled with customer data</p>
<p className="text-sm text-gray-500 dark:text-gray-400">Search and select a customer. Variables are automatically filled with their data.</p>
</div>
</div>
<div className="flex items-start gap-3">
@@ -120,8 +265,8 @@ const HelpContracts: React.FC = () => {
<span className="text-sm font-semibold text-brand-600 dark:text-brand-400">3</span>
</div>
<div>
<h4 className="font-medium text-gray-900 dark:text-white">Send for Signature</h4>
<p className="text-sm text-gray-500 dark:text-gray-400">Customer receives an email with a secure signing link</p>
<h4 className="font-medium text-gray-900 dark:text-white">Send Immediately (Optional)</h4>
<p className="text-sm text-gray-500 dark:text-gray-400">Check the box to email the signing request right away, or uncheck to send later.</p>
</div>
</div>
<div className="flex items-start gap-3">
@@ -130,7 +275,7 @@ const HelpContracts: React.FC = () => {
</div>
<div>
<h4 className="font-medium text-gray-900 dark:text-white">Track Status</h4>
<p className="text-sm text-gray-500 dark:text-gray-400">Monitor pending, signed, expired, or voided contracts</p>
<p className="text-sm text-gray-500 dark:text-gray-400">Monitor the contract in the Sent Contracts list</p>
</div>
</div>
</div>
@@ -139,10 +284,10 @@ const HelpContracts: React.FC = () => {
<section className="mb-10">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
<Eye size={20} className="text-brand-500" /> Contract Status
<Eye size={20} className="text-brand-500" /> Contract Status & Actions
</h2>
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<div className="space-y-3">
<div className="space-y-3 mb-6">
<div className="flex items-center gap-3 p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg">
<div className="w-3 h-3 rounded-full bg-yellow-500" />
<span className="font-medium text-gray-900 dark:text-white">Pending</span>
@@ -164,6 +309,30 @@ const HelpContracts: React.FC = () => {
<span className="text-sm text-gray-500 dark:text-gray-400">- Contract was cancelled by business</span>
</div>
</div>
<h3 className="font-semibold text-gray-900 dark:text-white mb-3">Available Actions</h3>
<div className="space-y-2">
<div className="flex items-center gap-3 p-2 text-sm">
<Eye size={16} className="text-blue-500" />
<span className="text-gray-700 dark:text-gray-300"><strong>View Details</strong> - See full contract information</span>
</div>
<div className="flex items-center gap-3 p-2 text-sm">
<Copy size={16} className="text-blue-500" />
<span className="text-gray-700 dark:text-gray-300"><strong>Copy Link</strong> - Copy signing URL to clipboard (pending only)</span>
</div>
<div className="flex items-center gap-3 p-2 text-sm">
<Send size={16} className="text-green-500" />
<span className="text-gray-700 dark:text-gray-300"><strong>Send/Resend</strong> - Email the signing request (pending only)</span>
</div>
<div className="flex items-center gap-3 p-2 text-sm">
<ExternalLink size={16} className="text-blue-500" />
<span className="text-gray-700 dark:text-gray-300"><strong>Open Signing Page</strong> - View the customer signing experience</span>
</div>
<div className="flex items-center gap-3 p-2 text-sm">
<Ban size={16} className="text-red-500" />
<span className="text-gray-700 dark:text-gray-300"><strong>Void</strong> - Cancel a pending contract</span>
</div>
</div>
</div>
</section>
@@ -220,6 +389,10 @@ const HelpContracts: React.FC = () => {
Once a contract is signed, a PDF is automatically generated that includes:
</p>
<ul className="space-y-2">
<li className="flex items-start gap-2">
<ChevronRight size={16} className="text-brand-500 mt-1 flex-shrink-0" />
<span className="text-gray-700 dark:text-gray-300">Your business branding and logo</span>
</li>
<li className="flex items-start gap-2">
<ChevronRight size={16} className="text-brand-500 mt-1 flex-shrink-0" />
<span className="text-gray-700 dark:text-gray-300">The full contract content with substituted variables</span>
@@ -230,13 +403,18 @@ const HelpContracts: React.FC = () => {
</li>
<li className="flex items-start gap-2">
<ChevronRight size={16} className="text-brand-500 mt-1 flex-shrink-0" />
<span className="text-gray-700 dark:text-gray-300">Complete audit trail footer with all verification data</span>
<span className="text-gray-700 dark:text-gray-300">Complete audit trail with verification data</span>
</li>
<li className="flex items-start gap-2">
<ChevronRight size={16} className="text-brand-500 mt-1 flex-shrink-0" />
<span className="text-gray-700 dark:text-gray-300">Your business branding and logo</span>
<span className="text-gray-700 dark:text-gray-300">Legal notice about electronic signatures</span>
</li>
</ul>
<div className="mt-4 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
<p className="text-sm text-gray-600 dark:text-gray-300">
<strong>Preview PDFs:</strong> Use the eye icon on any template to preview how the final PDF will look with sample customer data.
</p>
</div>
</div>
</section>
@@ -252,15 +430,19 @@ const HelpContracts: React.FC = () => {
</li>
<li className="flex items-start gap-2">
<ChevronRight size={16} className="text-brand-500 mt-1 flex-shrink-0" />
<span className="text-gray-700 dark:text-gray-300"><strong>Set Expiration Dates:</strong> Use the expiration feature to ensure contracts are signed in a timely manner</span>
<span className="text-gray-700 dark:text-gray-300"><strong>Start as Draft:</strong> Create templates in Draft status, test with PDF preview, then activate</span>
</li>
<li className="flex items-start gap-2">
<ChevronRight size={16} className="text-brand-500 mt-1 flex-shrink-0" />
<span className="text-gray-700 dark:text-gray-300"><strong>Link to Services:</strong> Associate contracts with specific services for automatic requirement checking</span>
<span className="text-gray-700 dark:text-gray-300"><strong>Set Expiration Dates:</strong> Use the expiration feature to ensure contracts are signed promptly</span>
</li>
<li className="flex items-start gap-2">
<ChevronRight size={16} className="text-brand-500 mt-1 flex-shrink-0" />
<span className="text-gray-700 dark:text-gray-300"><strong>Version Control:</strong> Create new versions rather than editing existing active templates</span>
<span className="text-gray-700 dark:text-gray-300"><strong>Create Customers First:</strong> Ensure customers exist in the system before sending contracts</span>
</li>
<li className="flex items-start gap-2">
<ChevronRight size={16} className="text-brand-500 mt-1 flex-shrink-0" />
<span className="text-gray-700 dark:text-gray-300"><strong>Archive Old Templates:</strong> Rather than deleting, archive templates you no longer use</span>
</li>
<li className="flex items-start gap-2">
<ChevronRight size={16} className="text-brand-500 mt-1 flex-shrink-0" />