feat(01-04): implement WizardState types and pure reducer

- Create src/store/types.ts: WizardState interface, WizardAction union type, INITIAL_STATE constant
- Create src/store/reducer.ts: pure wizardReducer handling SET_STEP, SET_BACKEND_TYPE, SET_REMOTE_NAME, SET_REMOTE_PARAMS, SET_DEPLOYMENT, RESET
- All 8 reducer tests GREEN
- No localStorage/sessionStorage access (SECU-03 compliant)
This commit is contained in:
2026-03-26 10:23:53 +01:00
parent 5a4bb475c1
commit 3fade79ea1
2 changed files with 90 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
// src/store/reducer.ts
// Pure reducer — no side effects, no localStorage, no async operations.
import { WizardState, WizardAction, INITIAL_STATE } from './types';
export function wizardReducer(
state: WizardState = INITIAL_STATE,
action: WizardAction
): WizardState {
switch (action.type) {
case 'SET_STEP':
return { ...state, currentStep: action.payload };
case 'SET_BACKEND_TYPE':
return {
...state,
remote: { ...state.remote, backendType: action.payload },
};
case 'SET_REMOTE_NAME':
return {
...state,
remote: { ...state.remote, name: action.payload },
};
case 'SET_REMOTE_PARAMS':
return {
...state,
remote: { ...state.remote, params: action.payload },
};
case 'SET_DEPLOYMENT':
return {
...state,
deployment: { ...state.deployment, ...action.payload },
};
case 'RESET':
return INITIAL_STATE;
default:
return state;
}
}
+46
View File
@@ -0,0 +1,46 @@
// src/store/types.ts
// WizardState shape, action union type, and initial state.
// SECURITY: This state is exclusively in-memory.
// NEVER add localStorage.setItem, sessionStorage.setItem, or IndexedDB here.
// Closing the browser tab is the intended "clear credentials" operation (SECU-03).
import type { BackendType } from '../schemas/registry';
// Re-export BackendType so store consumers import from one place
export type { BackendType };
export interface WizardState {
currentStep: number;
remote: {
name: string;
backendType: BackendType | null;
params: Record<string, string>; // backend-specific key/value pairs (rclone config keys)
};
deployment: {
includeInstall: boolean;
configPath: 'machine-wide' | 'user-profile'; // machine-wide = C:\ProgramData\rclone\
scriptTargets: ('intune' | 'rmm')[];
};
}
export type WizardAction =
| { type: 'SET_STEP'; payload: number }
| { type: 'SET_BACKEND_TYPE'; payload: BackendType }
| { type: 'SET_REMOTE_NAME'; payload: string }
| { type: 'SET_REMOTE_PARAMS'; payload: Record<string, string> }
| { type: 'SET_DEPLOYMENT'; payload: Partial<WizardState['deployment']> }
| { type: 'RESET' };
export const INITIAL_STATE: WizardState = {
currentStep: 0,
remote: {
name: '',
backendType: null,
params: {},
},
deployment: {
includeInstall: false,
configPath: 'machine-wide',
scriptTargets: ['intune', 'rmm'],
},
};