chore: sync GovOPlaN module split state
This commit is contained in:
@@ -21,24 +21,24 @@ export function BasicsStep({ draft, patch }: WizardStepProps) {
|
||||
const campaign = asRecord(draft.campaign);
|
||||
return (
|
||||
<div className="form-grid">
|
||||
<FormField label="Campaign name" help="A human-readable name shown in lists and reports.">
|
||||
<FormField label="i18n:govoplan-campaign.campaign_name.aa5d0e72" help="i18n:govoplan-campaign.a_human_readable_name_shown_in_lists_and_reports.afc23e7e">
|
||||
<input value={getText(campaign, "name")} onChange={(event) => patch(["campaign", "name"], event.target.value)} />
|
||||
</FormField>
|
||||
<FormField label="Campaign ID" help="Stable technical identifier.">
|
||||
<FormField label="i18n:govoplan-campaign.campaign_id.4c4ed79e" help="i18n:govoplan-campaign.stable_technical_identifier.28c406b2">
|
||||
<input value={getText(campaign, "id")} onChange={(event) => patch(["campaign", "id"], event.target.value)} />
|
||||
</FormField>
|
||||
<FormField label="Mode">
|
||||
<FormField label="i18n:govoplan-campaign.mode.a7b93d21">
|
||||
<select value={getText(campaign, "mode", "draft")} onChange={(event) => patch(["campaign", "mode"], event.target.value)}>
|
||||
<option value="draft">Draft</option>
|
||||
<option value="test">Test</option>
|
||||
<option value="send">Send</option>
|
||||
<option value="draft">i18n:govoplan-campaign.draft.23d33e22</option>
|
||||
<option value="test">i18n:govoplan-campaign.test.640ab2ba</option>
|
||||
<option value="send">i18n:govoplan-campaign.send.9bc2575c</option>
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField label="Description">
|
||||
<FormField label="i18n:govoplan-campaign.description.55f8ebc8">
|
||||
<textarea rows={5} value={getText(campaign, "description")} onChange={(event) => patch(["campaign", "description"], event.target.value)} />
|
||||
</FormField>
|
||||
</div>
|
||||
);
|
||||
</div>);
|
||||
|
||||
}
|
||||
|
||||
export function SenderStep({ draft, patch }: WizardStepProps) {
|
||||
@@ -55,86 +55,86 @@ export function SenderStep({ draft, patch }: WizardStepProps) {
|
||||
const imapAppend = asRecord(delivery.imap_append_sent);
|
||||
return (
|
||||
<div className="form-grid">
|
||||
<FormField label="Default From address">
|
||||
<FormField label="i18n:govoplan-campaign.default_from_address.b9ee6d77">
|
||||
<EmailAddressInput
|
||||
value={from}
|
||||
suggestions={suggestions}
|
||||
allowMultiple
|
||||
addLabel="Add From"
|
||||
emptyText="No global From address configured."
|
||||
onChange={(addresses: MailboxAddress[]) => patch(["recipients", "from"], addresses)}
|
||||
/>
|
||||
addLabel="i18n:govoplan-campaign.add_from.a095bb50"
|
||||
emptyText="i18n:govoplan-campaign.no_global_from_address_configured.28141d2b"
|
||||
onChange={(addresses: MailboxAddress[]) => patch(["recipients", "from"], addresses)} />
|
||||
|
||||
</FormField>
|
||||
<FormField label="Global recipients">
|
||||
<FormField label="i18n:govoplan-campaign.global_recipients.d9aaa427">
|
||||
<EmailAddressInput
|
||||
value={globalTo}
|
||||
suggestions={suggestions}
|
||||
allowMultiple
|
||||
addLabel="Add recipient"
|
||||
emptyText="No global recipients configured."
|
||||
onChange={(addresses: MailboxAddress[]) => patch(["recipients", "to"], addresses)}
|
||||
/>
|
||||
addLabel="i18n:govoplan-campaign.add_recipient.a989d1f1"
|
||||
emptyText="i18n:govoplan-campaign.no_global_recipients_configured.d4e20e92"
|
||||
onChange={(addresses: MailboxAddress[]) => patch(["recipients", "to"], addresses)} />
|
||||
|
||||
</FormField>
|
||||
<FormField label="CC">
|
||||
<FormField label="i18n:govoplan-campaign.cc.c5a976de">
|
||||
<EmailAddressInput
|
||||
value={globalCc}
|
||||
suggestions={suggestions}
|
||||
allowMultiple
|
||||
addLabel="Add CC"
|
||||
emptyText="No global CC recipients configured."
|
||||
onChange={(addresses: MailboxAddress[]) => patch(["recipients", "cc"], addresses)}
|
||||
/>
|
||||
addLabel="i18n:govoplan-campaign.add_cc.bcb39ea3"
|
||||
emptyText="i18n:govoplan-campaign.no_global_cc_recipients_configured.8afb23c1"
|
||||
onChange={(addresses: MailboxAddress[]) => patch(["recipients", "cc"], addresses)} />
|
||||
|
||||
</FormField>
|
||||
<FormField label="BCC">
|
||||
<FormField label="i18n:govoplan-campaign.bcc.4c0145a3">
|
||||
<EmailAddressInput
|
||||
value={globalBcc}
|
||||
suggestions={suggestions}
|
||||
allowMultiple
|
||||
addLabel="Add BCC"
|
||||
emptyText="No global BCC recipients configured."
|
||||
onChange={(addresses: MailboxAddress[]) => patch(["recipients", "bcc"], addresses)}
|
||||
/>
|
||||
addLabel="i18n:govoplan-campaign.add_bcc.ae9feacc"
|
||||
emptyText="i18n:govoplan-campaign.no_global_bcc_recipients_configured.86ef64ab"
|
||||
onChange={(addresses: MailboxAddress[]) => patch(["recipients", "bcc"], addresses)} />
|
||||
|
||||
</FormField>
|
||||
<FormField label="Reply-To">
|
||||
<FormField label="i18n:govoplan-campaign.reply_to.c1733667">
|
||||
<EmailAddressInput
|
||||
value={globalReplyTo.slice(0, 1)}
|
||||
suggestions={suggestions}
|
||||
allowMultiple={false}
|
||||
showAddButton={false}
|
||||
addLabel={globalReplyTo.length ? "Replace" : "Add Reply-To"}
|
||||
emptyText="No Reply-To address configured."
|
||||
onChange={(addresses: MailboxAddress[]) => patch(["recipients", "reply_to"], addresses.slice(0, 1))}
|
||||
/>
|
||||
addLabel={globalReplyTo.length ? "i18n:govoplan-campaign.replace.a7cf7b25" : "i18n:govoplan-campaign.add_reply_to.84b195f4"}
|
||||
emptyText="i18n:govoplan-campaign.no_reply_to_address_configured.0665c1d9"
|
||||
onChange={(addresses: MailboxAddress[]) => patch(["recipients", "reply_to"], addresses.slice(0, 1))} />
|
||||
|
||||
</FormField>
|
||||
<FormField label="SMTP host"><input value={getText(smtp, "host")} onChange={(event) => patch(["server", "smtp", "host"], event.target.value)} /></FormField>
|
||||
<FormField label="SMTP port"><input type="number" value={getNumber(smtp, "port", 587)} onChange={(event) => patch(["server", "smtp", "port"], Number(event.target.value || 0))} /></FormField>
|
||||
<ToggleSwitch label="Append successful messages to Sent via IMAP" checked={getBool(imapAppend, "enabled")} onChange={(checked) => patch(["delivery", "imap_append_sent", "enabled"], checked)} />
|
||||
</div>
|
||||
);
|
||||
<FormField label="i18n:govoplan-campaign.smtp_host.2d4a434b"><input value={getText(smtp, "host")} onChange={(event) => patch(["server", "smtp", "host"], event.target.value)} /></FormField>
|
||||
<FormField label="i18n:govoplan-campaign.smtp_port.65b5a108"><input type="number" value={getNumber(smtp, "port", 587)} onChange={(event) => patch(["server", "smtp", "port"], Number(event.target.value || 0))} /></FormField>
|
||||
<ToggleSwitch label="i18n:govoplan-campaign.append_successful_messages_to_sent_via_imap.dbd1b1d8" checked={getBool(imapAppend, "enabled")} onChange={(checked) => patch(["delivery", "imap_append_sent", "enabled"], checked)} />
|
||||
</div>);
|
||||
|
||||
}
|
||||
|
||||
export function FieldsStep({ draft, patchRoot }: { draft: Record<string, unknown>; patchRoot: (key: string, value: unknown) => void }) {
|
||||
export function FieldsStep({ draft, patchRoot }: {draft: Record<string, unknown>;patchRoot: (key: string, value: unknown) => void;}) {
|
||||
return (
|
||||
<div>
|
||||
<div className="step-intro">
|
||||
<h2>Campaign fields</h2>
|
||||
<p>Define reusable fields for templates, attachment rules, ZIP passwords and recipient data.</p>
|
||||
<h2>i18n:govoplan-campaign.campaign_fields.969e7d80</h2>
|
||||
<p>i18n:govoplan-campaign.define_reusable_fields_for_templates_attachment_.6424f892</p>
|
||||
</div>
|
||||
<JsonEditor value={draft.fields ?? []} onValid={(value) => patchRoot("fields", value)} />
|
||||
</div>
|
||||
);
|
||||
</div>);
|
||||
|
||||
}
|
||||
|
||||
export function RecipientsStep({ draft, patchRoot }: { draft: Record<string, unknown>; patchRoot: (key: string, value: unknown) => void }) {
|
||||
export function RecipientsStep({ draft, patchRoot }: {draft: Record<string, unknown>;patchRoot: (key: string, value: unknown) => void;}) {
|
||||
return (
|
||||
<div>
|
||||
<div className="step-intro">
|
||||
<h2>Recipients</h2>
|
||||
<p>Store inline recipients or source/mapping configuration. A table editor will replace this JSON editor in the recipient section pass.</p>
|
||||
<h2>i18n:govoplan-campaign.recipients.78cbf8eb</h2>
|
||||
<p>i18n:govoplan-campaign.store_inline_recipients_or_source_mapping_config.77bbe98f</p>
|
||||
</div>
|
||||
<JsonEditor value={draft.entries ?? { inline: [] }} onValid={(value) => patchRoot("entries", value)} />
|
||||
</div>
|
||||
);
|
||||
</div>);
|
||||
|
||||
}
|
||||
|
||||
export function TemplateStep({ draft, patch }: WizardStepProps) {
|
||||
@@ -142,16 +142,16 @@ export function TemplateStep({ draft, patch }: WizardStepProps) {
|
||||
return (
|
||||
<div>
|
||||
<div className="step-intro">
|
||||
<h2>Template</h2>
|
||||
<p>Compose the subject and body. Merge fields can later be inserted from the field picker.</p>
|
||||
<h2>i18n:govoplan-campaign.template.3ec1ae06</h2>
|
||||
<p>i18n:govoplan-campaign.compose_the_subject_and_body_merge_fields_can_la.4fb6e97b</p>
|
||||
</div>
|
||||
<div className="form-grid">
|
||||
<FormField label="Subject"><input value={getText(template, "subject")} onChange={(event) => patch(["template", "subject"], event.target.value)} /></FormField>
|
||||
<FormField label="Plain text body"><textarea rows={12} value={getText(template, "text")} onChange={(event) => patch(["template", "text"], event.target.value)} /></FormField>
|
||||
<FormField label="HTML body"><textarea rows={8} value={getText(template, "html")} onChange={(event) => patch(["template", "html"], event.target.value)} /></FormField>
|
||||
<FormField label="i18n:govoplan-campaign.subject.8d183dbd"><input value={getText(template, "subject")} onChange={(event) => patch(["template", "subject"], event.target.value)} /></FormField>
|
||||
<FormField label="i18n:govoplan-campaign.plain_text_body.030a0da0"><textarea rows={12} value={getText(template, "text")} onChange={(event) => patch(["template", "text"], event.target.value)} /></FormField>
|
||||
<FormField label="i18n:govoplan-campaign.html_body.77b5ba37"><textarea rows={8} value={getText(template, "html")} onChange={(event) => patch(["template", "html"], event.target.value)} /></FormField>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>);
|
||||
|
||||
}
|
||||
|
||||
export function AttachmentsStep({ draft, patch }: WizardStepProps) {
|
||||
@@ -159,36 +159,36 @@ export function AttachmentsStep({ draft, patch }: WizardStepProps) {
|
||||
return (
|
||||
<div>
|
||||
<div className="step-intro">
|
||||
<h2>Attachments</h2>
|
||||
<p>Configure campaign-wide attachment behavior and global matching rules.</p>
|
||||
<h2>i18n:govoplan-campaign.attachments.6771ade6</h2>
|
||||
<p>i18n:govoplan-campaign.configure_campaign_wide_attachment_behavior_and_.441a6cd2</p>
|
||||
</div>
|
||||
<div className="form-grid compact responsive-form-grid">
|
||||
<FormField label="Campaign attachment base path"><input value={getText(attachments, "base_path", ".")} onChange={(event) => patch(["attachments", "base_path"], event.target.value)} /></FormField>
|
||||
<FormField label="Missing behavior"><select value={getText(attachments, "missing_behavior", "ask")} onChange={(event) => patch(["attachments", "missing_behavior"], event.target.value)}>{behaviorOptions.map((option) => <option key={option}>{option}</option>)}</select></FormField>
|
||||
<FormField label="Ambiguous behavior"><select value={getText(attachments, "ambiguous_behavior", "ask")} onChange={(event) => patch(["attachments", "ambiguous_behavior"], event.target.value)}>{behaviorOptions.map((option) => <option key={option}>{option}</option>)}</select></FormField>
|
||||
<ToggleSwitch label="Allow individual attachments" checked={getBool(attachments, "allow_individual")} onChange={(checked) => patch(["attachments", "allow_individual"], checked)} />
|
||||
<FormField label="i18n:govoplan-campaign.campaign_attachment_base_path.84827619"><input value={getText(attachments, "base_path", ".")} onChange={(event) => patch(["attachments", "base_path"], event.target.value)} /></FormField>
|
||||
<FormField label="i18n:govoplan-campaign.missing_behavior.de3d1ac7"><select value={getText(attachments, "missing_behavior", "ask")} onChange={(event) => patch(["attachments", "missing_behavior"], event.target.value)}>{behaviorOptions.map((option) => <option key={option}>{option}</option>)}</select></FormField>
|
||||
<FormField label="i18n:govoplan-campaign.ambiguous_behavior.e86e724b"><select value={getText(attachments, "ambiguous_behavior", "ask")} onChange={(event) => patch(["attachments", "ambiguous_behavior"], event.target.value)}>{behaviorOptions.map((option) => <option key={option}>{option}</option>)}</select></FormField>
|
||||
<ToggleSwitch label="i18n:govoplan-campaign.allow_individual_attachments.a6ff0e87" checked={getBool(attachments, "allow_individual")} onChange={(checked) => patch(["attachments", "allow_individual"], checked)} />
|
||||
</div>
|
||||
<JsonEditor value={attachments.global ?? []} onValid={(value) => patch(["attachments", "global"], value)} />
|
||||
</div>
|
||||
);
|
||||
</div>);
|
||||
|
||||
}
|
||||
|
||||
export function ReviewStep({ version, onValidate }: { version: unknown; onValidate: () => void }) {
|
||||
export function ReviewStep({ version, onValidate }: {version: unknown;onValidate: () => void;}) {
|
||||
const record = asRecord(version);
|
||||
return (
|
||||
<div>
|
||||
<div className="step-intro">
|
||||
<h2>Review setup</h2>
|
||||
<p>Validate the campaign definition before building message drafts.</p>
|
||||
<h2>i18n:govoplan-campaign.review_setup.d0059acb</h2>
|
||||
<p>i18n:govoplan-campaign.validate_the_campaign_definition_before_building.d36ee1dc</p>
|
||||
</div>
|
||||
<div className="metric-grid inside">
|
||||
<MetricCard label="Errors" value={summaryValue(asRecord(record.validation_summary), ["error_count", "errors", "blocked"])} tone="danger" />
|
||||
<MetricCard label="Warnings" value={summaryValue(asRecord(record.validation_summary), ["warning_count", "warnings"])} tone="warning" />
|
||||
<MetricCard label="Built" value={summaryValue(asRecord(record.build_summary), ["built_count", "built", "messages_built"])} tone="info" />
|
||||
<MetricCard label="i18n:govoplan-campaign.errors.805e86a8" value={summaryValue(asRecord(record.validation_summary), ["error_count", "errors", "blocked"])} tone="danger" />
|
||||
<MetricCard label="i18n:govoplan-campaign.warnings.1430f976" value={summaryValue(asRecord(record.validation_summary), ["warning_count", "warnings"])} tone="warning" />
|
||||
<MetricCard label="i18n:govoplan-campaign.built.a6ad3f82" value={summaryValue(asRecord(record.build_summary), ["built_count", "built", "messages_built"])} tone="info" />
|
||||
</div>
|
||||
<Button variant="primary" onClick={onValidate}>Validate campaign</Button>
|
||||
</div>
|
||||
);
|
||||
<Button variant="primary" onClick={onValidate}>i18n:govoplan-campaign.validate_campaign.6934c1c2</Button>
|
||||
</div>);
|
||||
|
||||
}
|
||||
|
||||
export function SendStep({ draft, patch }: WizardStepProps) {
|
||||
@@ -197,19 +197,19 @@ export function SendStep({ draft, patch }: WizardStepProps) {
|
||||
return (
|
||||
<div>
|
||||
<div className="step-intro">
|
||||
<h2>Send preparation</h2>
|
||||
<p>Configure rate limits and prepare the final send workflow.</p>
|
||||
<h2>i18n:govoplan-campaign.send_preparation.6e078f43</h2>
|
||||
<p>i18n:govoplan-campaign.configure_rate_limits_and_prepare_the_final_send.af3d4e48</p>
|
||||
</div>
|
||||
<div className="form-grid compact responsive-form-grid">
|
||||
<FormField label="Messages per minute"><input type="number" min={1} value={getNumber(rateLimit, "messages_per_minute", 5)} onChange={(event) => patch(["delivery", "rate_limit", "messages_per_minute"], Number(event.target.value || 1))} /></FormField>
|
||||
<FormField label="Concurrency"><input type="number" min={1} value={getNumber(rateLimit, "concurrency", 1)} onChange={(event) => patch(["delivery", "rate_limit", "concurrency"], Number(event.target.value || 1))} /></FormField>
|
||||
<FormField label="i18n:govoplan-campaign.messages_per_minute.bea49348"><input type="number" min={1} value={getNumber(rateLimit, "messages_per_minute", 5)} onChange={(event) => patch(["delivery", "rate_limit", "messages_per_minute"], Number(event.target.value || 1))} /></FormField>
|
||||
<FormField label="i18n:govoplan-campaign.concurrency.2ec390bf"><input type="number" min={1} value={getNumber(rateLimit, "concurrency", 1)} onChange={(event) => patch(["delivery", "rate_limit", "concurrency"], Number(event.target.value || 1))} /></FormField>
|
||||
</div>
|
||||
<p className="muted">Test send and queue actions remain in the Send Wizard for now.</p>
|
||||
</div>
|
||||
);
|
||||
<p className="muted">i18n:govoplan-campaign.test_send_and_queue_actions_remain_in_the_send_w.60dee716</p>
|
||||
</div>);
|
||||
|
||||
}
|
||||
|
||||
function JsonEditor({ value, onValid }: { value: unknown; onValid: (value: unknown) => void }) {
|
||||
function JsonEditor({ value, onValid }: {value: unknown;onValid: (value: unknown) => void;}) {
|
||||
const [text, setText] = useState(stringifyJson(value));
|
||||
const [error, setError] = useState("");
|
||||
|
||||
@@ -228,8 +228,8 @@ function JsonEditor({ value, onValid }: { value: unknown; onValid: (value: unkno
|
||||
return (
|
||||
<div className="json-edit-block">
|
||||
<textarea rows={12} value={text} onChange={(event) => change(event.target.value)} />
|
||||
{error ? <p className="form-help danger-text">Invalid JSON: {error}</p> : <p className="form-help">Valid JSON is saved with the wizard draft.</p>}
|
||||
{Array.isArray(value) && value.length > 0 && <p className="form-help">Preview: {stringifyPreview(asArray(value)[0], 140)}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
{error ? <p className="form-help danger-text">i18n:govoplan-campaign.invalid_json.03f61ae0 {error}</p> : <p className="form-help">i18n:govoplan-campaign.valid_json_is_saved_with_the_wizard_draft.16f5d341</p>}
|
||||
{Array.isArray(value) && value.length > 0 && <p className="form-help">i18n:govoplan-campaign.preview.4bf30626 {stringifyPreview(asArray(value)[0], 140)}</p>}
|
||||
</div>);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user