OPC # 0001: Extract OPC into standalone repo

This commit is contained in:
amadzarak
2026-04-25 17:26:42 -04:00
commit 42383bdc03
170 changed files with 21365 additions and 0 deletions
@@ -0,0 +1,124 @@
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/provisioningApi';
import type { ProvisioningRequest } from '../../types/provisioning';
const EMPTY: ProvisioningRequest = {
clientName: '', stateCode: '', subdomain: '', adminEmail: '',
siteCode: '', environment: 'fdev', tier: '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>
);
}