diff --git a/frontend/src/components/SimulationList.tsx b/frontend/src/components/SimulationList.tsx
index cc705aa..210a1f6 100644
--- a/frontend/src/components/SimulationList.tsx
+++ b/frontend/src/components/SimulationList.tsx
@@ -44,7 +44,7 @@ export function SimulationList({ engagementId }: SimulationListProps): JSX.Eleme
className="btn-primary"
data-testid="new-simulation-btn"
>
- Nouvelle simulation
+ New simulation
) : undefined
}
@@ -62,7 +62,7 @@ export function SimulationList({ engagementId }: SimulationListProps): JSX.Eleme
className="btn-primary"
data-testid="new-simulation-btn"
>
- Nouvelle simulation
+ New simulation
) : null}
diff --git a/frontend/src/pages/SimulationFormPage.tsx b/frontend/src/pages/SimulationFormPage.tsx
index 45dbc88..2e274d9 100644
--- a/frontend/src/pages/SimulationFormPage.tsx
+++ b/frontend/src/pages/SimulationFormPage.tsx
@@ -137,7 +137,7 @@ export function SimulationFormPage(): JSX.Element {
}
try {
const created = await createMutation.mutateAsync({ name: rt.name.trim() });
- push('Simulation créée', 'success');
+ push('Simulation created', 'success');
navigate(`/engagements/${engagementId}/simulations/${created.id}/edit`);
} catch (err) {
setSubmitError(extractApiError(err, 'Could not create simulation'));
@@ -164,7 +164,7 @@ export function SimulationFormPage(): JSX.Element {
};
try {
await updateMutation.mutateAsync(patch);
- push('Simulation mise à jour', 'success');
+ push('Simulation updated', 'success');
} catch (err) {
setSubmitError(extractApiError(err, 'Could not update simulation'));
}
@@ -181,7 +181,7 @@ export function SimulationFormPage(): JSX.Element {
};
try {
await updateMutation.mutateAsync(patch);
- push('Rapport SOC mis à jour', 'success');
+ push('SOC report updated', 'success');
} catch (err) {
setSubmitError(extractApiError(err, 'Could not update SOC fields'));
}
@@ -190,18 +190,18 @@ export function SimulationFormPage(): JSX.Element {
const onMarkReview = async () => {
try {
await transitionMutation.mutateAsync('review_required');
- push('Simulation marquée en revue', 'success');
+ push('Simulation marked for review', 'success');
} catch (err) {
- push(extractApiError(err, 'Transition impossible'), 'error');
+ push(extractApiError(err, 'Transition failed'), 'error');
}
};
const onClose = async () => {
try {
await transitionMutation.mutateAsync('done');
- push('Simulation clôturée', 'success');
+ push('Simulation closed', 'success');
} catch (err) {
- push(extractApiError(err, 'Transition impossible'), 'error');
+ push(extractApiError(err, 'Transition failed'), 'error');
}
};
@@ -209,10 +209,10 @@ export function SimulationFormPage(): JSX.Element {
setShowDeleteConfirm(false);
try {
await deleteMutation.mutateAsync(simulationId as number);
- push('Simulation supprimée', 'success');
+ push('Simulation deleted', 'success');
navigate(`/engagements/${engagementId}`);
} catch (err) {
- push(extractApiError(err, 'Suppression impossible'), 'error');
+ push(extractApiError(err, 'Could not delete simulation'), 'error');
}
};
@@ -225,7 +225,7 @@ export function SimulationFormPage(): JSX.Element {
← Back to engagement
-
Nouvelle simulation
+ New simulation
{/* SOC card */}
@@ -442,13 +432,6 @@ export function SimulationFormPage(): JSX.Element {
/>
- {canSaveSoc && (
-
-
-
- )}
{submitError ? (
@@ -457,8 +440,18 @@ export function SimulationFormPage(): JSX.Element {
) : null}
- {/* Workflow + delete footer */}
-
+ {/* Unified sticky action bar */}
+
+ {canEditRT && (
+
+ )}
+ {canSaveSoc && (
+
+ )}
{showMarkReview && (
)}
{showClose && (
@@ -476,27 +469,27 @@ export function SimulationFormPage(): JSX.Element {
onClick={onClose}
disabled={transitionMutation.isPending}
>
- Clôturer
+ Close
)}
{canEditEngagements && simulationId && (
)}
{showDeleteConfirm && (
setShowDeleteConfirm(false)}
diff --git a/frontend/src/pages/UsersAdminPage.tsx b/frontend/src/pages/UsersAdminPage.tsx
index da008a6..4f286bd 100644
--- a/frontend/src/pages/UsersAdminPage.tsx
+++ b/frontend/src/pages/UsersAdminPage.tsx
@@ -110,7 +110,7 @@ export function UsersAdminPage(): JSX.Element {
Create account
-
{createError ? (
diff --git a/frontend/tests/SimulationFormPage.test.tsx b/frontend/tests/SimulationFormPage.test.tsx
index 2c6fd2f..7c2e04d 100644
--- a/frontend/tests/SimulationFormPage.test.tsx
+++ b/frontend/tests/SimulationFormPage.test.tsx
@@ -95,54 +95,54 @@ describe('SimulationFormPage — redteam mode (edit existing)', () => {
expect(screen.getByLabelText(/Executed at/i)).not.toBeDisabled();
});
- it('shows "Marquer en revue" button when status is pending', async () => {
+ it('shows "Mark for review" button when status is pending', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
- expect(screen.getByRole('button', { name: /Marquer en revue/i })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /Mark for review/i })).toBeInTheDocument();
});
});
- it('does not show "Clôturer" when status is pending', async () => {
+ it('does not show "Close" when status is pending', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
- await waitFor(() => screen.getByRole('button', { name: /Marquer en revue/i }));
- expect(screen.queryByRole('button', { name: /Clôturer/i })).toBeNull();
+ await waitFor(() => screen.getByRole('button', { name: /Mark for review/i }));
+ expect(screen.queryByRole('button', { name: /^Close$/i })).toBeNull();
});
- it('shows "Marquer en revue" for in_progress status', async () => {
+ it('shows "Mark for review" for in_progress status', async () => {
mock.onGet('/simulations/7').reply(200, { ...BASE_SIM, status: 'in_progress' });
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
- expect(screen.getByRole('button', { name: /Marquer en revue/i })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /Mark for review/i })).toBeInTheDocument();
});
});
- it('shows "Clôturer" button when status is review_required', async () => {
+ it('shows "Close" button when status is review_required', async () => {
mock.onGet('/simulations/7').reply(200, { ...BASE_SIM, status: 'review_required' });
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
- expect(screen.getByRole('button', { name: /Clôturer/i })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /^Close$/i })).toBeInTheDocument();
});
});
- it('shows "Supprimer" button for redteam', async () => {
+ it('shows "Delete" button for redteam', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
- expect(screen.getByRole('button', { name: /Supprimer/i })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /^Delete$/i })).toBeInTheDocument();
});
});
});
@@ -242,13 +242,13 @@ describe('SimulationFormPage — SOC role + review_required (can edit SOC fields
expect(screen.queryByTestId('soc-blocked-banner')).toBeNull();
});
- it('shows "Clôturer" for SOC when review_required', async () => {
+ it('shows "Close" for SOC when review_required', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
- expect(screen.getByRole('button', { name: /Clôturer/i })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /^Close$/i })).toBeInTheDocument();
});
});
});