Files
floci-ui/packages/frontend/src/components/CloudFormationCreatePanel.tsx
T
2026-09-30 17:43:42 -04:00

39 lines
4.0 KiB
TypeScript

import {useRef, useState} from 'react'
import {Upload} from 'lucide-react'
export function CloudFormationCreatePanel({isSubmitting, submitError, onCancel, onSubmit}: {isSubmitting: boolean; submitError: string | null; onCancel: () => void; onSubmit: (values: Record<string, unknown>) => void}) {
const [stackName, setStackName] = useState('')
const [templateBody, setTemplateBody] = useState('')
const [templateUrl, setTemplateUrl] = useState('')
const [parameters, setParameters] = useState('{}')
const [capabilities, setCapabilities] = useState('')
const [fileName, setFileName] = useState<string | null>(null)
const [fileError, setFileError] = useState<string | null>(null)
const fileInput = useRef<HTMLInputElement>(null)
async function loadTemplate(file?: File) {
if (!file) return
if (!/\.(json|ya?ml)$/i.test(file.name)) {
setFileError('Choose a JSON, YAML, or YML template file.')
return
}
setTemplateBody(await file.text())
setFileName(file.name)
setFileError(null)
}
const hasTemplate = Boolean(templateBody.trim() || templateUrl.trim())
return <form className="dynamic-form" onSubmit={(event) => { event.preventDefault(); if (hasTemplate) onSubmit({stackName, templateBody, templateUrl, parameters, capabilities}) }}>
<label className="dynamic-field"><span>Stack Name<em className="field-required">*</em></span><input className="input" value={stackName} required pattern="^[a-zA-Z][a-zA-Z0-9\-]{0,127}$" maxLength={128} onChange={(event) => setStackName(event.target.value)} placeholder="orders-stack"/><small>Start with a letter. Use letters, numbers, and hyphens.</small></label>
<label className="dynamic-field dynamic-field--span"><span>Template</span><textarea aria-label="Template" className="textarea code-textarea" value={templateBody} rows={14} spellCheck={false} onChange={(event) => setTemplateBody(event.target.value)} placeholder="Paste CloudFormation JSON or YAML, or upload a template file."/><small>Provide this or a Template S3 URL below.</small></label>
<input ref={fileInput} type="file" accept=".json,.yaml,.yml,application/json,application/x-yaml,text/yaml" hidden onChange={(event) => void loadTemplate(event.target.files?.[0])}/>
<div style={{gridColumn: '1 / -1', display: 'flex', gap: '8px', alignItems: 'center'}}><button className="button" type="button" disabled={isSubmitting} onClick={() => fileInput.current?.click()}><Upload size={14}/> Upload JSON or YAML</button>{fileName && <span className="muted compact-text">Loaded {fileName}</span>}</div>
<label className="dynamic-field dynamic-field--span"><span>Template S3 URL</span><input aria-label="Template S3 URL" className="input" value={templateUrl} onChange={(event) => setTemplateUrl(event.target.value)} placeholder="https://bucket.s3.amazonaws.com/templates/stack.yaml"/><small>Provide this or an inline/uploaded template above.</small></label>
<label className="dynamic-field dynamic-field--span"><span>Parameters (JSON)</span><textarea className="textarea code-textarea small" value={parameters} onChange={(event) => setParameters(event.target.value)} spellCheck={false}/><small>Optional object of parameter keys to string values.</small></label>
<label className="dynamic-field dynamic-field--span"><span>Capabilities</span><input className="input" value={capabilities} onChange={(event) => setCapabilities(event.target.value)} placeholder="CAPABILITY_IAM, CAPABILITY_NAMED_IAM"/><small>Optional comma-separated AWS capabilities.</small></label>
{fileError && <div className="form-error" role="alert">{fileError}</div>}
{submitError && <div className="form-error" role="alert">{submitError}</div>}
<div style={{gridColumn: '1 / -1', justifySelf: 'end', display: 'flex', gap: '8px'}}><button className="button" type="button" disabled={isSubmitting} onClick={onCancel}>Cancel</button><button className="button primary" type="submit" disabled={isSubmitting || !hasTemplate}>Create stack</button></div>
</form>
}