- Updated configuration and deployment files - Improved system architecture and components - Enhanced documentation and testing - Fixed various issues and added new features 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
303 lines
10 KiB
TypeScript
303 lines
10 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import {
|
|
KeyIcon,
|
|
CheckCircleIcon,
|
|
ExclamationTriangleIcon,
|
|
UserIcon,
|
|
DocumentTextIcon
|
|
} from '@heroicons/react/24/outline'
|
|
|
|
interface LicenseValidationProps {
|
|
systemInfo: any
|
|
configData: any
|
|
onComplete: (data: any) => void
|
|
onBack?: () => void
|
|
isCompleted: boolean
|
|
}
|
|
|
|
interface LicenseData {
|
|
email: string
|
|
licenseKey: string
|
|
organizationName?: string
|
|
acceptedAt?: string
|
|
}
|
|
|
|
export default function LicenseValidation({
|
|
systemInfo,
|
|
configData,
|
|
onComplete,
|
|
onBack,
|
|
isCompleted
|
|
}: LicenseValidationProps) {
|
|
const [licenseData, setLicenseData] = useState<LicenseData>({
|
|
email: configData?.license?.email || '',
|
|
licenseKey: configData?.license?.licenseKey || '',
|
|
organizationName: configData?.license?.organizationName || ''
|
|
})
|
|
|
|
const [validating, setValidating] = useState(false)
|
|
const [validationResult, setValidationResult] = useState<{
|
|
valid: boolean
|
|
message: string
|
|
details?: any
|
|
} | null>(null)
|
|
const [error, setError] = useState('')
|
|
|
|
// Email validation function
|
|
const isValidEmail = (email: string): boolean => {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
return emailRegex.test(email)
|
|
}
|
|
|
|
// Check if form is ready for validation
|
|
const canValidate = licenseData.email &&
|
|
isValidEmail(licenseData.email) &&
|
|
licenseData.licenseKey
|
|
|
|
const validateLicense = async () => {
|
|
if (!licenseData.email || !licenseData.licenseKey) {
|
|
setError('Both email and license key are required')
|
|
return
|
|
}
|
|
|
|
if (!isValidEmail(licenseData.email)) {
|
|
setError('Please enter a valid email address')
|
|
return
|
|
}
|
|
|
|
setValidating(true)
|
|
setError('')
|
|
setValidationResult(null)
|
|
|
|
try {
|
|
const response = await fetch('/api/setup/license/validate', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
email: licenseData.email,
|
|
licenseKey: licenseData.licenseKey,
|
|
organizationName: licenseData.organizationName
|
|
}),
|
|
})
|
|
|
|
const result = await response.json()
|
|
|
|
if (response.ok && result.valid) {
|
|
setValidationResult({
|
|
valid: true,
|
|
message: result.message || 'License validated successfully',
|
|
details: result.details
|
|
})
|
|
} else {
|
|
setValidationResult({
|
|
valid: false,
|
|
message: result.message || 'License validation failed',
|
|
details: result.details
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('License validation error:', error)
|
|
setValidationResult({
|
|
valid: false,
|
|
message: 'Failed to validate license. Please check your connection and try again.'
|
|
})
|
|
} finally {
|
|
setValidating(false)
|
|
}
|
|
}
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
|
|
if (!licenseData.email || !licenseData.licenseKey) {
|
|
setError('Both email and license key are required')
|
|
return
|
|
}
|
|
|
|
if (!validationResult?.valid) {
|
|
setError('Please validate your license before continuing')
|
|
return
|
|
}
|
|
|
|
setError('')
|
|
onComplete({
|
|
license: {
|
|
...licenseData,
|
|
validatedAt: new Date().toISOString(),
|
|
validationDetails: validationResult.details
|
|
}
|
|
})
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="space-y-8">
|
|
|
|
{/* License Information */}
|
|
<div className="card">
|
|
<div className="flex items-center mb-4">
|
|
<KeyIcon className="h-6 w-6 text-bzzz-primary mr-2" />
|
|
<h3 className="text-lg font-medium text-gray-900">License Information</h3>
|
|
{validationResult?.valid && <CheckCircleIcon className="h-5 w-5 text-eucalyptus-600 ml-2" />}
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Email Address
|
|
</label>
|
|
<div className="relative">
|
|
<UserIcon className="h-5 w-5 text-gray-400 absolute left-3 top-1/2 transform -translate-y-1/2" />
|
|
<input
|
|
type="email"
|
|
value={licenseData.email}
|
|
onChange={(e) => setLicenseData(prev => ({ ...prev, email: e.target.value }))}
|
|
placeholder="your-email@company.com"
|
|
className={`w-full pl-10 pr-4 py-3 border rounded-lg focus:ring-bzzz-primary focus:border-bzzz-primary ${
|
|
licenseData.email && !isValidEmail(licenseData.email)
|
|
? 'border-red-300 bg-red-50'
|
|
: 'border-gray-300'
|
|
}`}
|
|
required
|
|
/>
|
|
</div>
|
|
{licenseData.email && !isValidEmail(licenseData.email) ? (
|
|
<p className="text-sm text-red-600 mt-1">Please enter a valid email address</p>
|
|
) : (
|
|
<p className="text-sm text-gray-500 mt-1">
|
|
The email address associated with your CHORUS:agents license
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
License Key
|
|
</label>
|
|
<div className="relative">
|
|
<KeyIcon className="h-5 w-5 text-gray-400 absolute left-3 top-1/2 transform -translate-y-1/2" />
|
|
<input
|
|
type="text"
|
|
value={licenseData.licenseKey}
|
|
onChange={(e) => setLicenseData(prev => ({ ...prev, licenseKey: e.target.value }))}
|
|
placeholder="BZZZ-XXXX-XXXX-XXXX-XXXX"
|
|
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-bzzz-primary focus:border-bzzz-primary font-mono"
|
|
required
|
|
/>
|
|
</div>
|
|
<p className="text-sm text-gray-500 mt-1">
|
|
Your unique CHORUS:agents license key (found in your purchase confirmation email).
|
|
Validation is powered by KACHING license authority.
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Organization Name (Optional)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={licenseData.organizationName}
|
|
onChange={(e) => setLicenseData(prev => ({ ...prev, organizationName: e.target.value }))}
|
|
placeholder="Your Company Name"
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-bzzz-primary focus:border-bzzz-primary"
|
|
/>
|
|
<p className="text-sm text-gray-500 mt-1">
|
|
Optional: Organization name for license tracking
|
|
</p>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={validateLicense}
|
|
disabled={validating || !canValidate}
|
|
className={`w-full py-3 px-4 rounded-lg font-medium transition-colors ${
|
|
validating || !canValidate
|
|
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
|
: 'bg-bzzz-primary text-white hover:bg-bzzz-primary-dark'
|
|
}`}
|
|
>
|
|
{validating ? 'Validating License...' : 'Validate License'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Validation Result */}
|
|
{validationResult && (
|
|
<div className={`panel ${validationResult.valid ? 'panel-success' : 'panel-error'}`}>
|
|
<div className="flex items-start">
|
|
<div className="flex-shrink-0">
|
|
{validationResult.valid ? (
|
|
<CheckCircleIcon className="h-6 w-6 text-eucalyptus-600 dark:text-eucalyptus-50" />
|
|
) : (
|
|
<ExclamationTriangleIcon className="h-6 w-6 text-coral-950 dark:text-coral-50" />
|
|
)}
|
|
</div>
|
|
<div className="ml-3">
|
|
<h4 className={`text-sm font-medium panel-title`}>
|
|
{validationResult.valid ? 'License Valid' : 'License Invalid'}
|
|
</h4>
|
|
<p className={`text-sm mt-1 panel-body`}>
|
|
{validationResult.message}
|
|
</p>
|
|
|
|
{validationResult.valid && validationResult.details && (
|
|
<div className="mt-3 text-sm panel-body">
|
|
<p><strong>License Type:</strong> {validationResult.details.licenseType || 'Standard'}</p>
|
|
<p><strong>Max Nodes:</strong> {validationResult.details.maxNodes || 'Unlimited'}</p>
|
|
<p><strong>Expires:</strong> {validationResult.details.expiresAt || 'Never'}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{error && (
|
|
<div className="flex items-center text-red-600 text-sm">
|
|
<ExclamationTriangleIcon className="h-4 w-4 mr-1" />
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{/* Need a License Panel */}
|
|
<div className="rounded-lg p-4 border bg-chorus-warm border-chorus-border-subtle dark:bg-mulberry-900 dark:border-chorus-border-defined">
|
|
<div className="flex items-start">
|
|
<DocumentTextIcon className="h-5 w-5 text-chorus-text-primary mt-0.5 mr-2 opacity-80" />
|
|
<div className="text-sm">
|
|
<h4 className="font-medium text-chorus-text-primary mb-1">Need a License?</h4>
|
|
<p className="text-chorus-text-secondary">
|
|
If you don't have a CHORUS:agents license yet, you can:
|
|
</p>
|
|
<ul className="text-chorus-text-secondary mt-1 space-y-1 ml-4">
|
|
<li>• Visit <a href="https://chorus.services/bzzz" target="_blank" className="underline hover:no-underline text-chorus-text-primary">chorus.services/bzzz</a> to purchase a license</li>
|
|
<li>• Contact our sales team at <a href="mailto:sales@chorus.services" className="underline hover:no-underline text-chorus-text-primary">sales@chorus.services</a></li>
|
|
<li>• Request a trial license for evaluation purposes</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-between pt-6 border-t border-gray-200">
|
|
<div>
|
|
{onBack && (
|
|
<button type="button" onClick={onBack} className="btn-outline">
|
|
Back
|
|
</button>
|
|
)}
|
|
</div>
|
|
<button
|
|
type="submit"
|
|
disabled={!validationResult?.valid}
|
|
className={`${validationResult?.valid ? 'btn-primary' : 'btn-disabled'}`}
|
|
>
|
|
{isCompleted ? 'Continue' : 'Next: System Detection'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
)
|
|
}
|