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:
@@ -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> <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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user