feat(04-03): implement ReviewStep — all 10 requirement behaviors GREEN

- ReviewStep.tsx: live rclone.conf preview via useMemo+try/catch (CONF-02)
- Security acknowledgement checkbox gates all download/copy buttons (SECU-01)
- 'No data is sent to any server' privacy notice (SECU-02)
- 4 OutputBlocks with download (DOWN-01–04) and copy (DOWN-06, CONF-03)
- Download All (ZIP) button calls downloadZip with all 4 file entries (DOWN-05)
- Replaced all expect.fail stubs with real assertions — 10/10 tests GREEN
This commit is contained in:
2026-03-27 11:52:07 +01:00
parent 6bba008256
commit 259fff6527
2 changed files with 261 additions and 12 deletions
+128
View File
@@ -0,0 +1,128 @@
// src/components/wizard/ReviewStep.tsx
// Step 3 component — live preview + security gate + all download/copy actions.
// Covers CONF-02, CONF-03, DOWN-01DOWN-06, SECU-01, SECU-02.
import { useMemo, useState } from 'react';
import { useWizard } from '../../store/context';
import {
buildRcloneConf,
buildIntuneInstall,
buildIntuneDetection,
buildRmmScript,
} from '../../generators/index';
import { OutputBlock } from './OutputBlock';
import { downloadZip } from '../../utils/downloadZip';
const PLACEHOLDER = '# Fill in the wizard steps to generate your rclone.conf';
export function ReviewStep() {
const { state } = useWizard();
const [acknowledged, setAcknowledged] = useState(false);
// CONF-02: live preview — updates on every state change
const rcloneConf = useMemo(() => {
try {
return buildRcloneConf(state);
} catch {
return PLACEHOLDER;
}
}, [state]);
const intuneInstall = useMemo(() => {
try {
return buildIntuneInstall(state);
} catch {
return '';
}
}, [state]);
const intuneDetection = useMemo(() => {
try {
return buildIntuneDetection(state);
} catch {
return '';
}
}, [state]);
const rmmScript = useMemo(() => {
try {
return buildRmmScript(state);
} catch {
return '';
}
}, [state]);
// DOWN-05: ZIP bundle — always include all 4 files
async function handleDownloadZip() {
await downloadZip(
[
{ name: 'rclone.conf', content: rcloneConf },
{ name: 'intune-install.ps1', content: intuneInstall },
{ name: 'intune-detection.ps1', content: intuneDetection },
{ name: 'rmm-script.ps1', content: rmmScript },
],
'rclone-deployment.zip'
);
}
return (
<div>
{/* SECU-02: client-side only notice */}
<p className="text-sm text-green-700 bg-green-50 rounded p-3 mb-6">
No data is sent to any server. All file generation happens in your browser.
</p>
{/* SECU-01: security acknowledgement gate */}
<div className="bg-yellow-50 border border-yellow-300 rounded p-4 mb-6">
<p className="text-sm text-yellow-800 font-medium mb-2">
Security warning: generated files contain credentials in plain text.
</p>
<label className="flex items-center gap-2 text-sm text-yellow-900 cursor-pointer">
<input
type="checkbox"
checked={acknowledged}
onChange={(e) => setAcknowledged(e.target.checked)}
/>
I understand that the generated files contain credentials in plain text.
</label>
</div>
{/* Output blocks — DOWN-01 through DOWN-04, CONF-03, DOWN-06 */}
<OutputBlock
label="rclone.conf"
content={rcloneConf}
filename="rclone.conf"
disabled={!acknowledged}
/>
<OutputBlock
label="Intune Install Script"
content={intuneInstall}
filename="intune-install.ps1"
disabled={!acknowledged}
/>
<OutputBlock
label="Intune Detection Script"
content={intuneDetection}
filename="intune-detection.ps1"
disabled={!acknowledged}
/>
<OutputBlock
label="RMM Script"
content={rmmScript}
filename="rmm-script.ps1"
disabled={!acknowledged}
/>
{/* DOWN-05: ZIP bundle */}
<button
type="button"
disabled={!acknowledged}
onClick={handleDownloadZip}
className="w-full py-2 px-4 bg-blue-600 text-white rounded font-medium disabled:opacity-40 disabled:cursor-not-allowed hover:bg-blue-700"
>
Download All (ZIP)
</button>
</div>
);
}
export default ReviewStep;