feat(frontend): add BackLink helper + dedup 3 hand-rolled instances
BackLink: ArrowLeft (14px) + caption-md text-graphite hover:text-primary, instant. Replaces inline Link patterns in EngagementDetailPage (already committed), SimulationFormPage, and TemplateFormPage. Also migrates SimulationFormPage Done/SOC banners to AlertBanner. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
17
frontend/src/components/BackLink.tsx
Normal file
17
frontend/src/components/BackLink.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { ArrowLeft } from 'lucide-react';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface BackLinkProps {
|
||||||
|
to: string;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BackLink({ to, children }: BackLinkProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Link to={to} className="inline-flex items-center gap-xxs caption-md text-graphite hover:text-primary">
|
||||||
|
<ArrowLeft size={14} aria-hidden />
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@ import { MitreTechniquesField } from '@/components/MitreTechniquesField';
|
|||||||
import { ExecuteViaC2Modal } from '@/components/ExecuteViaC2Modal';
|
import { ExecuteViaC2Modal } from '@/components/ExecuteViaC2Modal';
|
||||||
import { ImportC2HistoryModal } from '@/components/ImportC2HistoryModal';
|
import { ImportC2HistoryModal } from '@/components/ImportC2HistoryModal';
|
||||||
import { C2TasksPanel } from '@/components/C2TasksPanel';
|
import { C2TasksPanel } from '@/components/C2TasksPanel';
|
||||||
|
import { AlertBanner } from '@/components/AlertBanner';
|
||||||
|
import { BackLink } from '@/components/BackLink';
|
||||||
|
|
||||||
interface RedteamFormState {
|
interface RedteamFormState {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -238,9 +240,7 @@ export function SimulationFormPage(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-xl max-w-2xl">
|
<div className="flex flex-col gap-xl max-w-2xl">
|
||||||
<header>
|
<header>
|
||||||
<Link to={`/engagements/${engagementId}`} className="btn-text-link text-[14px]">
|
<BackLink to={`/engagements/${engagementId}`}>Back to engagement</BackLink>
|
||||||
← Back to engagement
|
|
||||||
</Link>
|
|
||||||
<h1 className="text-[32px] font-medium leading-none mt-sm">New simulation</h1>
|
<h1 className="text-[32px] font-medium leading-none mt-sm">New simulation</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -279,9 +279,7 @@ export function SimulationFormPage(): JSX.Element {
|
|||||||
<div className="flex flex-col gap-xl">
|
<div className="flex flex-col gap-xl">
|
||||||
<header className="flex items-start justify-between gap-md">
|
<header className="flex items-start justify-between gap-md">
|
||||||
<div className="flex flex-col gap-sm">
|
<div className="flex flex-col gap-sm">
|
||||||
<Link to={`/engagements/${engagementId}`} className="btn-text-link text-[14px]">
|
<BackLink to={`/engagements/${engagementId}`}>Back to engagement</BackLink>
|
||||||
← Back to engagement
|
|
||||||
</Link>
|
|
||||||
<h1 className="text-[32px] font-medium leading-none">{rt.name || simulation?.name}</h1>
|
<h1 className="text-[32px] font-medium leading-none">{rt.name || simulation?.name}</h1>
|
||||||
{status ? (
|
{status ? (
|
||||||
<div className="flex items-center gap-md">
|
<div className="flex items-center gap-md">
|
||||||
@@ -298,22 +296,17 @@ export function SimulationFormPage(): JSX.Element {
|
|||||||
|
|
||||||
{/* Done banner */}
|
{/* Done banner */}
|
||||||
{isDone && (
|
{isDone && (
|
||||||
<div
|
<AlertBanner variant="success">
|
||||||
role="status"
|
|
||||||
className="rounded-none px-xl py-md bg-cloud border border-hairline text-[14px] text-charcoal"
|
|
||||||
>
|
|
||||||
This simulation is <strong>done</strong> and read-only. Use Reopen to make changes.
|
This simulation is <strong>done</strong> and read-only. Use Reopen to make changes.
|
||||||
</div>
|
</AlertBanner>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* SOC banner */}
|
{/* SOC banner */}
|
||||||
{socBlocked && (
|
{socBlocked && (
|
||||||
<div
|
<div data-testid="soc-blocked-banner">
|
||||||
role="alert"
|
<AlertBanner variant="warn">
|
||||||
data-testid="soc-blocked-banner"
|
Simulation not yet ready for review — the red team must mark it as "Review required" before you can fill in the SOC section.
|
||||||
className="rounded-none px-xl py-md bg-fog border border-hairline text-[14px] text-charcoal"
|
</AlertBanner>
|
||||||
>
|
|
||||||
Simulation not yet ready for review — the red team must mark it as "Review required" before you can fill in the SOC section.
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { FormField, TextArea, TextInput } from '@/components/FormField';
|
|||||||
import { LoadingState } from '@/components/LoadingState';
|
import { LoadingState } from '@/components/LoadingState';
|
||||||
import { ErrorState } from '@/components/ErrorState';
|
import { ErrorState } from '@/components/ErrorState';
|
||||||
import { ConfirmDialog } from '@/components/ConfirmDialog';
|
import { ConfirmDialog } from '@/components/ConfirmDialog';
|
||||||
|
import { BackLink } from '@/components/BackLink';
|
||||||
import { MitreTechniqueTag, MitreTacticTag } from '@/components/MitreTechniqueTag';
|
import { MitreTechniqueTag, MitreTacticTag } from '@/components/MitreTechniqueTag';
|
||||||
import { MitreTechniquePicker } from '@/components/MitreTechniquePicker';
|
import { MitreTechniquePicker } from '@/components/MitreTechniquePicker';
|
||||||
import { MitreMatrixModal } from '@/components/MitreMatrixModal';
|
import { MitreMatrixModal } from '@/components/MitreMatrixModal';
|
||||||
@@ -127,9 +128,7 @@ export function TemplateFormPage(): JSX.Element {
|
|||||||
<div className="flex flex-col gap-xl">
|
<div className="flex flex-col gap-xl">
|
||||||
<header className="flex items-start justify-between gap-md">
|
<header className="flex items-start justify-between gap-md">
|
||||||
<div className="flex flex-col gap-sm">
|
<div className="flex flex-col gap-sm">
|
||||||
<Link to="/admin/templates" className="btn-text-link text-[14px]">
|
<BackLink to="/admin/templates">Back to templates</BackLink>
|
||||||
← Back to templates
|
|
||||||
</Link>
|
|
||||||
<h1 className="text-[32px] font-medium leading-none">
|
<h1 className="text-[32px] font-medium leading-none">
|
||||||
{isNew ? 'New template' : (existing.data?.name ?? 'Edit template')}
|
{isNew ? 'New template' : (existing.data?.name ?? 'Edit template')}
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
Reference in New Issue
Block a user