Major updates and improvements to BZZZ system
- 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>
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user