Skip to content

Commit 95ee3f8

Browse files
authored
feat: Use EditableTextField for incident details (#57)
Makes title, description, and impact editable on the incident details page using the EditableTextField component. Also fixes the focus ring on the save/cancel buttons to use the native browser style.
1 parent 43f0216 commit 95ee3f8

4 files changed

Lines changed: 49 additions & 47 deletions

File tree

frontend/src/components/EditableTextField.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ const triggerStyles = cva(
3232
'rounded-radius-sm',
3333
'hover:bg-background-secondary',
3434
'hover:scale-110',
35-
'focus:outline-none',
36-
'focus:ring-2',
37-
'focus:ring-offset-2',
35+
'focus:outline-auto',
3836
'text-content-secondary',
3937
'hover:text-content-primary',
4038
'cursor-pointer',
@@ -99,9 +97,7 @@ const buttonBaseStyles = [
9997
'font-medium',
10098
'text-sm',
10199
'transition-colors',
102-
'focus:outline-none',
103-
'focus:ring-2',
104-
'focus:ring-offset-2',
100+
'focus:outline-auto',
105101
'disabled:opacity-50',
106102
'disabled:cursor-not-allowed',
107103
];

frontend/src/routes/$incidentId/components/IncidentSummary.test.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,13 @@ describe('IncidentSummary', () => {
8282
expect(screen.getByText('Users experiencing 500 errors')).toBeInTheDocument();
8383
});
8484

85-
it('renders empty state when impact is not present', () => {
85+
it('renders editable field when impact is empty', () => {
8686
const incidentWithoutImpact = {...mockIncident, impact: ''};
8787
renderWithQueryClient(<IncidentSummary incident={incidentWithoutImpact} />);
8888

8989
expect(screen.getByText('Impact')).toBeInTheDocument();
90-
expect(screen.getByText('No impact specified')).toBeInTheDocument();
90+
const editButtons = screen.getAllByRole('button', {name: 'Edit'});
91+
expect(editButtons.length).toBeGreaterThan(0);
9192
});
9293
});
9394

frontend/src/routes/$incidentId/components/IncidentSummary.tsx

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {useMutation, useQueryClient} from '@tanstack/react-query';
22
import {Card} from 'components/Card';
33
import {EditablePill} from 'components/EditablePill';
4+
import {EditableTextField} from 'components/EditableTextField';
45
import {Pill} from 'components/Pill';
56
import {Tag} from 'components/Tag';
67

@@ -33,21 +34,15 @@ export function IncidentSummary({incident}: IncidentSummaryProps) {
3334
updateIncidentFieldMutationOptions(queryClient)
3435
);
3536

36-
const handleSeverityChange = async (newSeverity: (typeof SEVERITY_OPTIONS)[number]) => {
37-
await updateIncidentField.mutateAsync({
38-
incidentId: incident.id,
39-
field: 'severity',
40-
value: newSeverity,
41-
});
42-
};
43-
44-
const handleStatusChange = async (newStatus: (typeof STATUS_OPTIONS)[number]) => {
45-
await updateIncidentField.mutateAsync({
46-
incidentId: incident.id,
47-
field: 'status',
48-
value: newStatus,
49-
});
50-
};
37+
const handleFieldChange =
38+
(field: 'severity' | 'status' | 'title' | 'description' | 'impact') =>
39+
async (value: string) => {
40+
await updateIncidentField.mutateAsync({
41+
incidentId: incident.id,
42+
field,
43+
value,
44+
});
45+
};
5146

5247
return (
5348
<Card>
@@ -67,63 +62,73 @@ export function IncidentSummary({incident}: IncidentSummaryProps) {
6762
<EditablePill
6863
value={incident.severity}
6964
options={SEVERITY_OPTIONS}
70-
onSave={handleSeverityChange}
65+
onSave={handleFieldChange('severity')}
7166
/>
7267
<EditablePill
7368
value={incident.status}
7469
options={STATUS_OPTIONS}
75-
onSave={handleStatusChange}
70+
onSave={handleFieldChange('status')}
7671
/>
7772
{incident.is_private && <Pill variant="private">Private</Pill>}
7873
</div>
79-
<Card.Title size="2xl">{incident.title}</Card.Title>
80-
<p className="text-content-secondary leading-comfortable">{incident.description}</p>
74+
<div className="mb-space-xl">
75+
<EditableTextField
76+
value={incident.title}
77+
onSave={handleFieldChange('title')}
78+
as="h3"
79+
className="text-content-headings text-2xl font-semibold"
80+
/>
81+
</div>
82+
<EditableTextField
83+
value={incident.description}
84+
onSave={handleFieldChange('description')}
85+
as="p"
86+
multiline
87+
className="text-content-secondary leading-comfortable"
88+
/>
8189

82-
<div className="mt-space-xl grid grid-cols-1 gap-space-xl md:grid-cols-3">
90+
<div className="mt-space-xl gap-space-xl grid grid-cols-1 md:grid-cols-3">
8391
<div>
84-
<h3 className="mb-space-md text-size-md font-semibold text-content-secondary">
85-
Impact
86-
</h3>
87-
{incident.impact ? (
88-
<p className="text-size-sm leading-comfortable text-content-secondary">
89-
{incident.impact}
90-
</p>
91-
) : (
92-
<p className="text-size-sm italic text-content-disabled">
93-
No impact specified
94-
</p>
95-
)}
92+
<EditableTextField
93+
value={incident.impact}
94+
onSave={handleFieldChange('impact')}
95+
label="Impact"
96+
labelClassName="text-size-md font-semibold"
97+
as="p"
98+
multiline
99+
className="text-size-sm leading-comfortable text-content-secondary"
100+
/>
96101
</div>
97102

98103
<div>
99-
<h3 className="mb-space-md text-size-md font-semibold text-content-secondary">
104+
<h3 className="mb-space-md text-size-md text-content-secondary font-semibold">
100105
Affected Areas
101106
</h3>
102107
{incident.affected_areas.length > 0 ? (
103-
<div className="flex flex-wrap gap-space-md">
108+
<div className="gap-space-md flex flex-wrap">
104109
{incident.affected_areas.map(area => (
105110
<Tag key={area}>{area}</Tag>
106111
))}
107112
</div>
108113
) : (
109-
<p className="text-size-sm italic text-content-disabled">
114+
<p className="text-size-sm text-content-disabled italic">
110115
No affected areas specified
111116
</p>
112117
)}
113118
</div>
114119

115120
<div>
116-
<h3 className="mb-space-md text-size-md font-semibold text-content-secondary">
121+
<h3 className="mb-space-md text-size-md text-content-secondary font-semibold">
117122
Root Cause
118123
</h3>
119124
{incident.root_causes.length > 0 ? (
120-
<div className="flex flex-wrap gap-space-md">
125+
<div className="gap-space-md flex flex-wrap">
121126
{incident.root_causes.map(cause => (
122127
<Tag key={cause}>{cause}</Tag>
123128
))}
124129
</div>
125130
) : (
126-
<p className="text-size-sm italic text-content-disabled">
131+
<p className="text-size-sm text-content-disabled italic">
127132
No root cause specified
128133
</p>
129134
)}

frontend/src/routes/$incidentId/queries/updateIncidentFieldMutationOptions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {SEVERITY_OPTIONS, STATUS_OPTIONS} from './incidentDetailQueryOptions';
77

88
export interface UpdateIncidentFieldArgs {
99
incidentId: string;
10-
field: 'severity' | 'status';
10+
field: 'severity' | 'status' | 'title' | 'description' | 'impact';
1111
value: string;
1212
}
1313

0 commit comments

Comments
 (0)