github-issue-tower-defence-management 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.github/workflows/umino-project.yml +0 -8
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +1 -1
  4. package/bin/adapter/entry-points/cli/index.js +3 -1
  5. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  6. package/bin/adapter/entry-points/console/consoleAirplaneSnapshotApi.js +347 -0
  7. package/bin/adapter/entry-points/console/consoleAirplaneSnapshotApi.js.map +1 -0
  8. package/bin/adapter/entry-points/console/consoleOperationApi.js +12 -0
  9. package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
  10. package/bin/adapter/entry-points/console/ui-dist/assets/index-BSa2JTna.js +88 -0
  11. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  12. package/bin/adapter/entry-points/console/webServer.js +28 -0
  13. package/bin/adapter/entry-points/console/webServer.js.map +1 -1
  14. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +5 -1
  15. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  16. package/bin/adapter/repositories/projectFieldDefinition.js +1 -0
  17. package/bin/adapter/repositories/projectFieldDefinition.js.map +1 -1
  18. package/jest.config.js +1 -0
  19. package/package.json +1 -1
  20. package/src/adapter/entry-points/cli/index.ts +4 -1
  21. package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.test.ts +658 -0
  22. package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.ts +482 -0
  23. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +84 -0
  24. package/src/adapter/entry-points/console/consoleOperationApi.ts +17 -0
  25. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +6 -0
  26. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +5 -1
  27. package/src/adapter/entry-points/console/ui/jest-globals.cjs +26 -0
  28. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.test.tsx +10 -0
  29. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.tsx +23 -16
  30. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.stories.tsx +56 -0
  31. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.test.tsx +111 -0
  32. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.tsx +92 -0
  33. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +15 -0
  34. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +19 -0
  35. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.tsx +30 -1
  36. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleErrorToast.stories.tsx +10 -0
  37. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.test.tsx +7 -1
  38. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.tsx +14 -0
  39. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.test.tsx +1 -3
  40. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.tsx +8 -7
  41. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.test.ts +304 -0
  42. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.ts +167 -0
  43. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.test.ts +15 -0
  44. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.ts +6 -1
  45. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.test.ts +77 -0
  46. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +48 -3
  47. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.test.ts +51 -0
  48. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.ts +39 -0
  49. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +215 -0
  50. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +20 -1
  51. package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.test.ts +361 -0
  52. package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.ts +401 -0
  53. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +4 -0
  54. package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.test.ts +22 -0
  55. package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.ts +6 -0
  56. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.ts +5 -1
  57. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +209 -0
  58. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +27 -2
  59. package/src/adapter/entry-points/console/ui-dist/assets/index-BSa2JTna.js +88 -0
  60. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  61. package/src/adapter/entry-points/console/webServer.test.ts +189 -0
  62. package/src/adapter/entry-points/console/webServer.ts +38 -0
  63. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +63 -8
  64. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +7 -1
  65. package/src/adapter/repositories/projectFieldDefinition.test.ts +1 -0
  66. package/src/adapter/repositories/projectFieldDefinition.ts +1 -0
  67. package/src/domain/entities/Project.ts +1 -0
  68. package/src/domain/usecases/ClearPastNextActionDateHourUseCase.test.ts +1 -0
  69. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  70. package/types/adapter/entry-points/console/consoleAirplaneSnapshotApi.d.ts +90 -0
  71. package/types/adapter/entry-points/console/consoleAirplaneSnapshotApi.d.ts.map +1 -0
  72. package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
  73. package/types/adapter/entry-points/console/webServer.d.ts +1 -0
  74. package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
  75. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  76. package/types/adapter/repositories/projectFieldDefinition.d.ts.map +1 -1
  77. package/types/domain/entities/Project.d.ts +1 -0
  78. package/types/domain/entities/Project.d.ts.map +1 -1
  79. package/bin/adapter/entry-points/console/ui-dist/assets/index-CaxYBHiT.js +0 -86
  80. package/src/adapter/entry-points/console/ui-dist/assets/index-CaxYBHiT.js +0 -86
@@ -147,22 +147,29 @@ export const ConsoleFileDiff = ({
147
147
  useState<ConsoleInlineCommentTarget | null>(null);
148
148
 
149
149
  if (patch === null || patch === '') {
150
- if (
151
- path !== undefined &&
152
- isImageFilePath(path) &&
153
- rawUrl !== undefined &&
154
- rawUrl !== null &&
155
- buildImageProxyUrl !== undefined
156
- ) {
157
- return (
158
- <div className="console-file-diff-image">
159
- <img
160
- className="console-file-diff-image-content"
161
- src={buildImageProxyUrl(rawUrl)}
162
- alt={path}
163
- />
164
- </div>
165
- );
150
+ if (path !== undefined && rawUrl !== undefined && rawUrl !== null) {
151
+ if (rawUrl.startsWith('data:image/')) {
152
+ return (
153
+ <div className="console-file-diff-image">
154
+ <img
155
+ className="console-file-diff-image-content"
156
+ src={rawUrl}
157
+ alt={path}
158
+ />
159
+ </div>
160
+ );
161
+ }
162
+ if (isImageFilePath(path) && buildImageProxyUrl !== undefined) {
163
+ return (
164
+ <div className="console-file-diff-image">
165
+ <img
166
+ className="console-file-diff-image-content"
167
+ src={buildImageProxyUrl(rawUrl)}
168
+ alt={path}
169
+ />
170
+ </div>
171
+ );
172
+ }
166
173
  }
167
174
  return (
168
175
  <p className="console-file-diff-empty">(no diff / binary or too large)</p>
@@ -0,0 +1,56 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ConsoleAirplaneModeButton } from './ConsoleAirplaneModeButton';
3
+
4
+ const meta: Meta<typeof ConsoleAirplaneModeButton> = {
5
+ title: 'Console/ConsoleAirplaneModeButton',
6
+ component: ConsoleAirplaneModeButton,
7
+ args: {
8
+ status: 'off',
9
+ progress: null,
10
+ capturedAt: null,
11
+ failures: [],
12
+ onStartSync: () => {},
13
+ onTurnOff: () => {},
14
+ },
15
+ };
16
+
17
+ export default meta;
18
+
19
+ type Story = StoryObj<typeof ConsoleAirplaneModeButton>;
20
+
21
+ export const Off: Story = {
22
+ args: {
23
+ status: 'off',
24
+ },
25
+ };
26
+
27
+ export const SyncingNoProgress: Story = {
28
+ args: {
29
+ status: 'syncing',
30
+ progress: { fetched: 0, total: 0 },
31
+ },
32
+ };
33
+
34
+ export const SyncingWithProgress: Story = {
35
+ args: {
36
+ status: 'syncing',
37
+ progress: { fetched: 127, total: 557 },
38
+ },
39
+ };
40
+
41
+ export const On: Story = {
42
+ args: {
43
+ status: 'on',
44
+ capturedAt: '2026-08-18T11:30:00Z',
45
+ },
46
+ };
47
+
48
+ export const SyncError: Story = {
49
+ args: {
50
+ status: 'error',
51
+ failures: [
52
+ 'https://github.com/owner/repo/issues/99',
53
+ 'https://github.com/owner/repo/pull/100',
54
+ ],
55
+ },
56
+ };
@@ -0,0 +1,111 @@
1
+ import { fireEvent, render } from '@testing-library/react';
2
+ import {
3
+ ConsoleAirplaneModeButton,
4
+ type ConsoleAirplaneModeButtonProps,
5
+ } from './ConsoleAirplaneModeButton';
6
+
7
+ const baseProps: ConsoleAirplaneModeButtonProps = {
8
+ status: 'off',
9
+ progress: null,
10
+ capturedAt: null,
11
+ failures: [],
12
+ onStartSync: jest.fn(),
13
+ onTurnOff: jest.fn(),
14
+ };
15
+
16
+ describe('ConsoleAirplaneModeButton', () => {
17
+ it('renders a start button when status is off', () => {
18
+ const { getByRole } = render(<ConsoleAirplaneModeButton {...baseProps} />);
19
+ const button = getByRole('button', { name: /airplane mode/i });
20
+ expect(button).not.toBeNull();
21
+ });
22
+
23
+ it('calls onStartSync when the start button is clicked', () => {
24
+ const onStartSync = jest.fn();
25
+ const { getByRole } = render(
26
+ <ConsoleAirplaneModeButton
27
+ {...baseProps}
28
+ status="off"
29
+ onStartSync={onStartSync}
30
+ />,
31
+ );
32
+ fireEvent.click(getByRole('button', { name: /airplane mode/i }));
33
+ expect(onStartSync).toHaveBeenCalledTimes(1);
34
+ });
35
+
36
+ it('shows progress text when status is syncing', () => {
37
+ const { getByRole } = render(
38
+ <ConsoleAirplaneModeButton
39
+ {...baseProps}
40
+ status="syncing"
41
+ progress={{ fetched: 10, total: 100 }}
42
+ />,
43
+ );
44
+ const status = getByRole('status');
45
+ expect(status.textContent).toContain('10/100');
46
+ });
47
+
48
+ it('shows Syncing… when progress total is zero', () => {
49
+ const { getByRole } = render(
50
+ <ConsoleAirplaneModeButton
51
+ {...baseProps}
52
+ status="syncing"
53
+ progress={{ fetched: 0, total: 0 }}
54
+ />,
55
+ );
56
+ expect(getByRole('status').textContent).toContain('Syncing');
57
+ });
58
+
59
+ it('does not show a done/on indicator while syncing', () => {
60
+ const { queryByText } = render(
61
+ <ConsoleAirplaneModeButton
62
+ {...baseProps}
63
+ status="syncing"
64
+ progress={{ fetched: 3, total: 10 }}
65
+ />,
66
+ );
67
+ expect(queryByText(/turn off/i)).toBeNull();
68
+ });
69
+
70
+ it('shows capturedAt and turn-off button when status is on', () => {
71
+ const { getByText } = render(
72
+ <ConsoleAirplaneModeButton
73
+ {...baseProps}
74
+ status="on"
75
+ capturedAt="2026-01-01T00:00:00Z"
76
+ />,
77
+ );
78
+ expect(getByText(/2026/)).not.toBeNull();
79
+ expect(getByText(/turn off/i)).not.toBeNull();
80
+ });
81
+
82
+ it('calls onTurnOff when turn-off button is clicked', () => {
83
+ const onTurnOff = jest.fn();
84
+ const { getByRole } = render(
85
+ <ConsoleAirplaneModeButton
86
+ {...baseProps}
87
+ status="on"
88
+ capturedAt="2026-01-01T00:00:00Z"
89
+ onTurnOff={onTurnOff}
90
+ />,
91
+ );
92
+ fireEvent.click(getByRole('button', { name: /turn off/i }));
93
+ expect(onTurnOff).toHaveBeenCalledTimes(1);
94
+ });
95
+
96
+ it('shows error state with retry button when status is error', () => {
97
+ const onStartSync = jest.fn();
98
+ const { getByRole, getByText } = render(
99
+ <ConsoleAirplaneModeButton
100
+ {...baseProps}
101
+ status="error"
102
+ failures={['https://github.com/o/r/issues/1']}
103
+ onStartSync={onStartSync}
104
+ />,
105
+ );
106
+ expect(getByText(/sync failed/i)).not.toBeNull();
107
+ const retryBtn = getByRole('button', { name: /retry/i });
108
+ fireEvent.click(retryBtn);
109
+ expect(onStartSync).toHaveBeenCalledTimes(1);
110
+ });
111
+ });
@@ -0,0 +1,92 @@
1
+ import type {
2
+ AirplaneModeStatus,
3
+ AirplaneSyncProgress,
4
+ } from '../../hooks/useAirplaneMode';
5
+
6
+ export type ConsoleAirplaneModeButtonProps = {
7
+ status: AirplaneModeStatus;
8
+ progress: AirplaneSyncProgress | null;
9
+ capturedAt: string | null;
10
+ failures: string[];
11
+ onStartSync: () => void;
12
+ onTurnOff: () => void;
13
+ };
14
+
15
+ const progressLabel = (progress: AirplaneSyncProgress | null): string => {
16
+ if (progress === null || progress.total === 0) {
17
+ return 'Syncing…';
18
+ }
19
+ return `Syncing ${progress.fetched}/${progress.total}`;
20
+ };
21
+
22
+ export const ConsoleAirplaneModeButton = ({
23
+ status,
24
+ progress,
25
+ capturedAt,
26
+ failures,
27
+ onStartSync,
28
+ onTurnOff,
29
+ }: ConsoleAirplaneModeButtonProps) => {
30
+ if (status === 'on') {
31
+ return (
32
+ <div className="console-airplane-mode console-airplane-mode--on">
33
+ <span className="console-airplane-mode-label">
34
+ ✈ Airplane mode
35
+ {capturedAt !== null
36
+ ? `: ${new Date(capturedAt).toLocaleString()}`
37
+ : ''}
38
+ </span>
39
+ <button
40
+ type="button"
41
+ className="console-airplane-mode-toggle"
42
+ onClick={onTurnOff}
43
+ >
44
+ Turn off
45
+ </button>
46
+ </div>
47
+ );
48
+ }
49
+
50
+ if (status === 'syncing') {
51
+ return (
52
+ <div className="console-airplane-mode console-airplane-mode--syncing">
53
+ <span
54
+ className="console-airplane-mode-progress"
55
+ role="status"
56
+ aria-live="polite"
57
+ >
58
+ {progressLabel(progress)}
59
+ </span>
60
+ </div>
61
+ );
62
+ }
63
+
64
+ if (status === 'error') {
65
+ return (
66
+ <div className="console-airplane-mode console-airplane-mode--error">
67
+ <span className="console-airplane-mode-error-label" role="alert">
68
+ Sync failed{failures.length > 0 ? `: ${failures.length} item(s)` : ''}
69
+ </span>
70
+ <button
71
+ type="button"
72
+ className="console-airplane-mode-toggle"
73
+ onClick={onStartSync}
74
+ >
75
+ Retry
76
+ </button>
77
+ </div>
78
+ );
79
+ }
80
+
81
+ return (
82
+ <div className="console-airplane-mode console-airplane-mode--off">
83
+ <button
84
+ type="button"
85
+ className="console-airplane-mode-toggle"
86
+ onClick={onStartSync}
87
+ >
88
+ ✈ Airplane mode
89
+ </button>
90
+ </div>
91
+ );
92
+ };
@@ -16,6 +16,13 @@ const meta: Meta<typeof ConsoleTabList> = {
16
16
  tabHref: (tab: ConsoleTabName) => `/projects/acme/${tab}`,
17
17
  onSelectTab: () => {},
18
18
  onSelectProject: () => {},
19
+ airplaneModeEnabled: true,
20
+ airplaneModeStatus: 'off',
21
+ airplaneModeProgress: null,
22
+ airplaneModeCapturedAt: null,
23
+ airplaneModeFailures: [],
24
+ onAirplaneModeStartSync: () => {},
25
+ onAirplaneModeTurnOff: () => {},
19
26
  },
20
27
  };
21
28
 
@@ -39,6 +46,14 @@ export const AllTabsWithCounts: Story = {
39
46
  },
40
47
  };
41
48
 
49
+ export const AirplaneModeDisabled: Story = {
50
+ args: {
51
+ activeTab: 'prs',
52
+ counts,
53
+ airplaneModeEnabled: false,
54
+ },
55
+ };
56
+
42
57
  export const ZeroCountTabsHidden: Story = {
43
58
  args: {
44
59
  activeTab: 'prs',
@@ -19,6 +19,13 @@ const baseProps = {
19
19
  tabHref: (tab: ConsoleTabName) => `/projects/acme/${tab}`,
20
20
  onSelectTab: () => {},
21
21
  onSelectProject: () => {},
22
+ airplaneModeEnabled: true,
23
+ airplaneModeStatus: 'off' as const,
24
+ airplaneModeProgress: null,
25
+ airplaneModeCapturedAt: null,
26
+ airplaneModeFailures: [],
27
+ onAirplaneModeStartSync: () => {},
28
+ onAirplaneModeTurnOff: () => {},
22
29
  };
23
30
 
24
31
  describe('ConsoleTabList', () => {
@@ -230,4 +237,16 @@ describe('ConsoleTabList', () => {
230
237
  baseElement.querySelector('.console-tab-pjname-dropdown'),
231
238
  ).toBeNull();
232
239
  });
240
+
241
+ it('does not render the airplane mode button when airplaneModeEnabled is false', () => {
242
+ const { baseElement } = render(
243
+ <ConsoleTabList
244
+ {...baseProps}
245
+ activeTab="prs"
246
+ counts={counts}
247
+ airplaneModeEnabled={false}
248
+ />,
249
+ );
250
+ expect(baseElement.querySelector('.console-airplane-mode')).toBeNull();
251
+ });
233
252
  });
@@ -1,6 +1,11 @@
1
1
  import { type ReactNode, useEffect, useRef, useState } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
+ import type {
4
+ AirplaneModeStatus,
5
+ AirplaneSyncProgress,
6
+ } from '../../hooks/useAirplaneMode';
3
7
  import { CONSOLE_TABS, type ConsoleTabName } from '../../logic/types';
8
+ import { ConsoleAirplaneModeButton } from './ConsoleAirplaneModeButton';
4
9
 
5
10
  export type ConsoleTabBarProps = {
6
11
  activeTab: ConsoleTabName;
@@ -13,6 +18,13 @@ export type ConsoleTabBarProps = {
13
18
  onSelectTab: (tab: ConsoleTabName) => void;
14
19
  onSelectProject: (pjcode: string) => void;
15
20
  settingsButton?: ReactNode;
21
+ airplaneModeEnabled: boolean;
22
+ airplaneModeStatus: AirplaneModeStatus;
23
+ airplaneModeProgress: AirplaneSyncProgress | null;
24
+ airplaneModeCapturedAt: string | null;
25
+ airplaneModeFailures: string[];
26
+ onAirplaneModeStartSync: () => void;
27
+ onAirplaneModeTurnOff: () => void;
16
28
  };
17
29
 
18
30
  export const ConsoleTabList = ({
@@ -26,6 +38,13 @@ export const ConsoleTabList = ({
26
38
  onSelectTab,
27
39
  onSelectProject,
28
40
  settingsButton,
41
+ airplaneModeEnabled,
42
+ airplaneModeStatus,
43
+ airplaneModeProgress,
44
+ airplaneModeCapturedAt,
45
+ airplaneModeFailures,
46
+ onAirplaneModeStartSync,
47
+ onAirplaneModeTurnOff,
29
48
  }: ConsoleTabBarProps) => {
30
49
  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
31
50
  const [dropdownPos, setDropdownPos] = useState<{
@@ -153,7 +172,7 @@ export const ConsoleTabList = ({
153
172
  )}
154
173
  </div>
155
174
  )}
156
- {generatedAt !== null && (
175
+ {generatedAt !== null && airplaneModeStatus !== 'on' && (
157
176
  <span
158
177
  className="console-tab-geninfo"
159
178
  data-from-cache={fromCache ? 'true' : undefined}
@@ -164,6 +183,16 @@ export const ConsoleTabList = ({
164
183
  {settingsButton !== undefined && (
165
184
  <span className="console-tab-settings">{settingsButton}</span>
166
185
  )}
186
+ {airplaneModeEnabled && (
187
+ <ConsoleAirplaneModeButton
188
+ status={airplaneModeStatus}
189
+ progress={airplaneModeProgress}
190
+ capturedAt={airplaneModeCapturedAt}
191
+ failures={airplaneModeFailures}
192
+ onStartSync={onAirplaneModeStartSync}
193
+ onTurnOff={onAirplaneModeTurnOff}
194
+ />
195
+ )}
167
196
  </nav>
168
197
  );
169
198
  };
@@ -5,6 +5,7 @@ const meta: Meta<typeof ConsoleErrorToast> = {
5
5
  title: 'Console/ConsoleErrorToast',
6
6
  component: ConsoleErrorToast,
7
7
  args: {
8
+ title: 'Approved — PR #851',
8
9
  message: 'Operation failed: HTTP 422 Review cannot be requested',
9
10
  onDismiss: () => {},
10
11
  },
@@ -18,6 +19,15 @@ export const ReviewRejectedByGitHub: Story = {};
18
19
 
19
20
  export const NetworkFailure: Story = {
20
21
  args: {
22
+ title: 'Approved — PR #851',
21
23
  message: 'Operation failed: network down',
22
24
  },
23
25
  };
26
+
27
+ export const AirplaneModeGuard: Story = {
28
+ args: {
29
+ title: 'Airplane mode',
30
+ message:
31
+ 'Operation failed: This action requires a network connection. Turn off airplane mode and try again.',
32
+ },
33
+ };
@@ -2,13 +2,15 @@ import { fireEvent, render } from '@testing-library/react';
2
2
  import { ConsoleNextActionDateActions } from './ConsoleNextActionDateActions';
3
3
 
4
4
  describe('ConsoleNextActionDateActions', () => {
5
- it('shows +1 day and +1 week outside manual triage tabs', () => {
5
+ it('shows +1 hour, +3 hours, +1 day and +1 week outside manual triage tabs', () => {
6
6
  const { getByText, queryByText } = render(
7
7
  <ConsoleNextActionDateActions
8
8
  isManualTriage={false}
9
9
  onSetNextActionDate={() => {}}
10
10
  />,
11
11
  );
12
+ expect(getByText('+1 hour')).toBeInTheDocument();
13
+ expect(getByText('+3 hours')).toBeInTheDocument();
12
14
  expect(getByText('+1 day')).toBeInTheDocument();
13
15
  expect(getByText('+1 week')).toBeInTheDocument();
14
16
  expect(queryByText('+1 week and skip')).toBeNull();
@@ -32,9 +34,13 @@ describe('ConsoleNextActionDateActions', () => {
32
34
  onSetNextActionDate={onSetNextActionDate}
33
35
  />,
34
36
  );
37
+ fireEvent.click(getByText('+1 hour'));
38
+ fireEvent.click(getByText('+3 hours'));
35
39
  fireEvent.click(getByText('+1 day'));
36
40
  fireEvent.click(getByText('+1 week'));
37
41
  expect(onSetNextActionDate.mock.calls.map((call) => call[0])).toEqual([
42
+ 'snooze_1hour',
43
+ 'snooze_3hours',
38
44
  'snooze_1day',
39
45
  'snooze_1week',
40
46
  ]);
@@ -10,6 +10,20 @@ export const ConsoleNextActionDateActions = ({
10
10
  onSetNextActionDate,
11
11
  }: ConsoleNextActionDateGroupProps) => (
12
12
  <div className="console-op-group">
13
+ <button
14
+ type="button"
15
+ className="console-op-button console-op-button-snooze"
16
+ onClick={() => onSetNextActionDate('snooze_1hour')}
17
+ >
18
+ +1 hour
19
+ </button>
20
+ <button
21
+ type="button"
22
+ className="console-op-button console-op-button-snooze"
23
+ onClick={() => onSetNextActionDate('snooze_3hours')}
24
+ >
25
+ +3 hours
26
+ </button>
13
27
  <button
14
28
  type="button"
15
29
  className="console-op-button console-op-button-snooze"
@@ -62,9 +62,7 @@ describe('ConsoleErrorToast', () => {
62
62
  />,
63
63
  );
64
64
  expect(getByText('Operation failed: HTTP 422')).toBeInTheDocument();
65
- expect(
66
- container.querySelector('.console-undo-toast-error'),
67
- ).toBeInTheDocument();
65
+ expect(container.querySelector('.console-error-toast')).toBeInTheDocument();
68
66
  expect(getByRole('alert')).toBeInTheDocument();
69
67
  });
70
68
 
@@ -36,23 +36,24 @@ export const ConsoleUndoToast = ({
36
36
  );
37
37
 
38
38
  export type ConsoleErrorToastProps = {
39
+ title?: string;
39
40
  message: string;
40
41
  onDismiss: () => void;
41
42
  };
42
43
 
43
44
  export const ConsoleErrorToast = ({
45
+ title,
44
46
  message,
45
47
  onDismiss,
46
48
  }: ConsoleErrorToastProps) => (
47
- <div
48
- className="console-undo-toast console-undo-toast-error"
49
- role="alert"
50
- aria-live="assertive"
51
- >
52
- <span className="console-undo-toast-message">{message}</span>
49
+ <div className="console-error-toast" role="alert" aria-live="assertive">
50
+ {title !== undefined && (
51
+ <span className="console-error-toast-title">{title}</span>
52
+ )}
53
+ <span className="console-error-toast-message">{message}</span>
53
54
  <button
54
55
  type="button"
55
- className="console-undo-toast-undo"
56
+ className="console-error-toast-dismiss"
56
57
  onClick={onDismiss}
57
58
  >
58
59
  Dismiss