feat(09-06): MergeTab multi-file UI + PFX convert option + render tests

- actions.ts: mergeCertsAction(files, outputFormat, password?) builds FormData with
  multiple file fields; delegates to postForm('merge', ...) (T-09-02/T-09-04)
- MergeTab.tsx: multi-file state (local), file input (multiple), output selector
  (pem|pfx), Zusammenfuehren button disabled when < 2 files (data-testid for tests),
  onOutputFormatChange callback to page.tsx for shared PasswordField visibility
- ConvertTab.tsx: gains pfx option + onTargetFormatChange callback (same pattern)
- page.tsx: lifts mergeOutputFormat + convertOutputFormat state; showPassword now
  also true when active tab's output format is 'pfx'; passes callbacks to tabs
- cert-manager.test.tsx: 5 new tests — MergeTab disabled/enabled by file count,
  shared PasswordField appears on pfx output, downloadBase64 called on success;
  ConvertTab pfx option present; all 19/19 web tests green
- All production cert-manager files type-clean (pre-existing test type issues unchanged)
This commit is contained in:
2026-07-02 07:52:52 +02:00
parent 6326064ad3
commit 8b15a0099f
5 changed files with 299 additions and 15 deletions
@@ -25,6 +25,10 @@ function isPfxFile(file: File | null): boolean {
* Layout per UI-SPEC: max-w-4xl, shared input card, tab nav, tab content card.
* T-09-02: password lives in local React state only; never logged or placed in URLs.
* T-09-04: all API calls via postForm() which sends credentials:'include'.
*
* The shared PasswordField is shown when:
* 1. A PFX/P12 file is selected in the shared DropZone, OR
* 2. The active tab (Merge or Convert) has PFX chosen as the output format.
*/
export default function CertManagerPage() {
const t = useTranslations('certManager');
@@ -34,7 +38,15 @@ export default function CertManagerPage() {
const [pemText, setPemText] = useState('');
const [password, setPassword] = useState('');
const showPassword = isPfxFile(file);
// Track the output format of the active Merge and Convert tabs
// so the shared PasswordField can be shown when PFX output is chosen.
const [mergeOutputFormat, setMergeOutputFormat] = useState('pem');
const [convertOutputFormat, setConvertOutputFormat] = useState('pem');
const showPassword =
isPfxFile(file) ||
(activeTab === 'merge' && mergeOutputFormat === 'pfx') ||
(activeTab === 'convert' && convertOutputFormat === 'pfx');
const handleFile = (selected: File) => {
setFile(selected);
@@ -61,16 +73,27 @@ export default function CertManagerPage() {
};
const renderActiveTab = () => {
const props = { file, pemText, password };
switch (activeTab) {
case 'inspect':
return <InspectTab {...props} />;
return <InspectTab file={file} pemText={pemText} password={password} />;
case 'split':
return <SplitTab {...props} />;
return <SplitTab file={file} pemText={pemText} password={password} />;
case 'merge':
return <MergeTab {...props} />;
return (
<MergeTab
password={password}
onOutputFormatChange={setMergeOutputFormat}
/>
);
case 'convert':
return <ConvertTab {...props} />;
return (
<ConvertTab
file={file}
pemText={pemText}
password={password}
onTargetFormatChange={setConvertOutputFormat}
/>
);
}
};