127 lines
4.4 KiB
TypeScript
127 lines
4.4 KiB
TypeScript
import { useState } from 'react';
|
|
import { Button, Intent } from '@blueprintjs/core';
|
|
import ClientDetailsStep from './ClientDetailsStep';
|
|
import DeploymentConfigStep from './DeploymentConfigStep';
|
|
import ReviewStep from './ReviewStep';
|
|
import DeploymentLiveStep from './DeploymentLiveStep';
|
|
import { submitProvisioningJob } from '../../api/tenantApi';
|
|
import { defaultStackConfig } from '../../types/provisioning';
|
|
import type { ProvisioningRequest } from '../../types/provisioning';
|
|
|
|
const EMPTY: ProvisioningRequest = {
|
|
clientName: '', stateCode: '', subdomain: '', adminEmail: '',
|
|
siteCode: '', environment: 'fdev', tier: 'Shared',
|
|
stackConfig: defaultStackConfig('Shared'),
|
|
};
|
|
|
|
const STEP_LABELS = ['Client Details', 'Deployment Config', 'Review', 'Deploying'];
|
|
|
|
interface Props {
|
|
onClose: () => void;
|
|
}
|
|
|
|
export default function DeployWizard({ onClose }: Props) {
|
|
const [activeStep, setActiveStep] = useState(0);
|
|
const [formData, setFormData] = useState<ProvisioningRequest>(EMPTY);
|
|
const [step0Valid, setStep0Valid] = useState(false);
|
|
const [step1Valid, setStep1Valid] = useState(true); // tier has a default
|
|
const [jobId, setJobId] = useState<string | null>(null);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
|
|
const handleChange = (u: Partial<ProvisioningRequest>) =>
|
|
setFormData((p) => ({ ...p, ...u }));
|
|
|
|
const handleDeploy = async () => {
|
|
setSubmitting(true);
|
|
setSubmitError(null);
|
|
try {
|
|
const id = await submitProvisioningJob(formData);
|
|
setJobId(id);
|
|
setActiveStep(3);
|
|
} catch (e: unknown) {
|
|
setSubmitError(e instanceof Error ? e.message : 'Deployment failed. Please try again.');
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const canGoBack = activeStep > 0 && !jobId;
|
|
const isLastFormStep = activeStep === 2;
|
|
|
|
return (
|
|
<div className="wizard-page">
|
|
{/* Header */}
|
|
<div className="wizard-page-header">
|
|
<div className="wizard-page-title">
|
|
<h2>Deploy New Client</h2>
|
|
<p>Provision a new Clarity tenant from scratch.</p>
|
|
</div>
|
|
{!jobId && (
|
|
<Button minimal icon="cross" onClick={onClose} />
|
|
)}
|
|
</div>
|
|
|
|
{/* Step progress */}
|
|
<div className="wizard-progress">
|
|
{STEP_LABELS.map((label, i) => (
|
|
<div
|
|
key={i}
|
|
className={`wizard-progress-step${i === activeStep ? ' active' : i < activeStep ? ' done' : ''}`}
|
|
>
|
|
<div className="wizard-progress-dot">{i < activeStep ? '✓' : i + 1}</div>
|
|
<span>{label}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Step content */}
|
|
<div className="wizard-page-body">
|
|
{activeStep === 0 && (
|
|
<ClientDetailsStep
|
|
data={formData}
|
|
onChange={handleChange}
|
|
signalParent={({ isValid }) => setStep0Valid(isValid)}
|
|
/>
|
|
)}
|
|
{activeStep === 1 && (
|
|
<DeploymentConfigStep
|
|
data={formData}
|
|
onChange={handleChange}
|
|
signalParent={({ isValid }) => setStep1Valid(isValid)}
|
|
/>
|
|
)}
|
|
{activeStep === 2 && (
|
|
<ReviewStep data={formData} signalParent={() => {}} />
|
|
)}
|
|
{activeStep === 3 && jobId && (
|
|
<DeploymentLiveStep jobId={jobId} subdomain={formData.subdomain} />
|
|
)}
|
|
|
|
{submitError && (
|
|
<p className="wizard-error" style={{ marginTop: 12 }}>{submitError}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Footer nav */}
|
|
{activeStep < 3 && (
|
|
<div className="wizard-page-footer">
|
|
{canGoBack && (
|
|
<Button text="Back" minimal icon="arrow-left" onClick={() => setActiveStep((s) => s - 1)} disabled={submitting} />
|
|
)}
|
|
<div style={{ flex: 1 }} />
|
|
{activeStep === 0 && (
|
|
<Button intent={Intent.PRIMARY} text="Next" rightIcon="arrow-right" disabled={!step0Valid} onClick={() => setActiveStep(1)} />
|
|
)}
|
|
{activeStep === 1 && (
|
|
<Button intent={Intent.PRIMARY} text="Next" rightIcon="arrow-right" disabled={!step1Valid} onClick={() => setActiveStep(2)} />
|
|
)}
|
|
{isLastFormStep && (
|
|
<Button intent={Intent.DANGER} text="Deploy Client" icon="cloud-upload" loading={submitting} onClick={handleDeploy} />
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|