import { useMemo, useState } from 'react'; import { Alert, Button, Toggle } from '@cameleer/design-system'; import { useRenderPreview } from '../../../api/queries/alertRules'; import { describeApiError } from '../../../api/errors'; import { toRequest, type FormState } from './form-state'; /** * Pure helper: returns a human-readable reason why saving should be blocked, * or null when the rule is safe to save (from the wizard's perspective). * * Currently covers: a rule with no webhooks AND no targets would be rejected * at the server edge (Task 3.3 validator) and would never notify anyone, so * the wizard blocks it earlier with a clear reason. The helper is exported * so `RuleEditorWizard` can also drive the Save button's `disabled` state * off the same single source of truth. */ export function computeSaveBlockReason(form: FormState): string | null { const noWebhooks = (form.webhooks ?? []).length === 0; const noTargets = (form.targets ?? []).length === 0; if (noWebhooks && noTargets) { return 'Add at least one webhook or target \u2014 a rule with no recipients never notifies anyone.'; } return null; } export function ReviewStep({ form, setForm, ruleId, }: { form: FormState; setForm?: (f: FormState) => void; /** * Present only in edit mode. When absent the notification-preview button is * hidden, because the backend `/render-preview` endpoint is id-bound and * has no stateless variant — rendering against an unsaved draft would * require a new endpoint and is explicitly out of scope here. */ ruleId?: string; }) { const req = toRequest(form); const saveBlockReason = useMemo(() => computeSaveBlockReason(form), [form]); const previewMutation = useRenderPreview(); const [preview, setPreview] = useState<{ title: string; message: string } | null>(null); const [previewError, setPreviewError] = useState(null); const onPreview = async () => { setPreviewError(null); try { const res = await previewMutation.mutateAsync({ id: ruleId!, req: {} }); setPreview({ title: res.title ?? '', message: res.message ?? '' }); } catch (e) { setPreview(null); setPreviewError(describeApiError(e)); } }; return (
{saveBlockReason && ( {saveBlockReason} )}
Name: {form.name}
Severity: {form.severity}
Scope: {form.scopeKind} {form.scopeKind !== 'env' && ` (app=${form.appSlug}${form.routeId ? `, route=${form.routeId}` : ''}${form.agentId ? `, agent=${form.agentId}` : ''})`}
Condition kind: {form.conditionKind}
Intervals: eval {form.evaluationIntervalSeconds}s · for {form.forDurationSeconds}s · re-notify {form.reNotifyMinutes}m
Targets: {form.targets.length}
Webhooks: {form.webhooks.length}
{setForm && (
setForm({ ...form, enabled: e.target.checked })} label="Enabled on save" />
)} {ruleId && (
{previewError && ( {previewError} )} {preview && (
Rendered notification preview
{preview.title || (empty title)}
                {preview.message || '(empty message)'}
              
)}
)}
Raw request JSON
          {JSON.stringify(req, null, 2)}
        
); }