diff --git a/apps/frontend/src/features/admin-form/settings/SettingsWebhooksPage.stories.tsx b/apps/frontend/src/features/admin-form/settings/SettingsWebhooksPage.stories.tsx
index 120d8c2872..533ab90a3b 100644
--- a/apps/frontend/src/features/admin-form/settings/SettingsWebhooksPage.stories.tsx
+++ b/apps/frontend/src/features/admin-form/settings/SettingsWebhooksPage.stories.tsx
@@ -1,4 +1,5 @@
import { Meta, StoryFn } from '@storybook/react'
+import { http, HttpResponse } from 'msw'
import { FormResponseMode, FormSettings } from 'formsg-shared/types/form'
@@ -75,6 +76,22 @@ Loading.parameters = {
msw: { handlers: { default: buildMswRoutes({ delay: 'infinite' }) } },
}
+export const Error = Template.bind({})
+Error.parameters = {
+ msw: {
+ handlers: {
+ default: [
+ http.get('/api/v3/admin/forms/:formId/settings', () =>
+ HttpResponse.json(
+ { message: 'Internal Server Error' },
+ { status: 500 },
+ ),
+ ),
+ ],
+ },
+ },
+}
+
export const Mobile = Template.bind({})
Mobile.parameters = {
...StorageModeRetryEnabled.parameters,
diff --git a/apps/frontend/src/features/admin-form/settings/SettingsWebhooksPage.test.tsx b/apps/frontend/src/features/admin-form/settings/SettingsWebhooksPage.test.tsx
new file mode 100644
index 0000000000..cf1f0feae3
--- /dev/null
+++ b/apps/frontend/src/features/admin-form/settings/SettingsWebhooksPage.test.tsx
@@ -0,0 +1,34 @@
+import { composeStories } from '@storybook/react'
+import { act, render, screen } from '@testing-library/react'
+
+import * as stories from './SettingsWebhooksPage.stories'
+
+const { Error: ErrorStory, UnsupportedEmailMode } = composeStories(stories)
+
+const UNSUPPORTED_MSG = /webhooks are only available in storage mode/i
+const ERROR_MSG = /couldn't load webhook settings/i
+
+describe('SettingsWebhooksPage', () => {
+ it('shows an error state, not the unsupported-mode message, when the settings fetch fails', async () => {
+ await act(async () => {
+ render()
+ })
+
+ await screen.findByText(ERROR_MSG)
+ // The error is announced to assistive tech (it appears after an async failure).
+ expect(screen.getByRole('alert')).toBeInTheDocument()
+ expect(
+ screen.getByRole('button', { name: /try again/i }),
+ ).toBeInTheDocument()
+ expect(screen.queryByText(UNSUPPORTED_MSG)).not.toBeInTheDocument()
+ })
+
+ it('still shows the unsupported-mode message for a form whose mode genuinely lacks webhook support', async () => {
+ await act(async () => {
+ render()
+ })
+
+ await screen.findByText(UNSUPPORTED_MSG)
+ expect(screen.queryByText(ERROR_MSG)).not.toBeInTheDocument()
+ })
+})
diff --git a/apps/frontend/src/features/admin-form/settings/SettingsWebhooksPage.tsx b/apps/frontend/src/features/admin-form/settings/SettingsWebhooksPage.tsx
index e05844a8df..0a9f50b510 100644
--- a/apps/frontend/src/features/admin-form/settings/SettingsWebhooksPage.tsx
+++ b/apps/frontend/src/features/admin-form/settings/SettingsWebhooksPage.tsx
@@ -10,13 +10,20 @@ import { useUser } from '~features/user/queries'
import { CategoryHeader } from './components/CategoryHeader'
import { WebhooksSection } from './components/WebhooksSection'
+import { WebhooksErrorMsg } from './components/WebhooksSection/WebhooksErrorMsg'
import { WebhooksUnsupportedMsg } from './components/WebhooksSection/WebhooksUnsupportedMsg'
import { useAdminFormSettings } from './queries'
export const SettingsWebhooksPage = (): JSX.Element => {
const { t } = useTranslation()
const gb = useGrowthBook()
- const { data: settings, isLoading } = useAdminFormSettings()
+ const {
+ data: settings,
+ isLoading,
+ isError,
+ isRefetching,
+ refetch,
+ } = useAdminFormSettings()
const userRes = useUser()
useEffect(() => {
@@ -27,9 +34,13 @@ export const SettingsWebhooksPage = (): JSX.Element => {
const enableMrfWebhooks = useFeatureIsOn(featureFlags.enableMrfWebhooks)
+ if (isError) {
+ return
+ }
+
const enableWebhooks =
!isLoading &&
- (settings?.responseMode == FormResponseMode.Encrypt ||
+ (settings?.responseMode === FormResponseMode.Encrypt ||
(settings?.responseMode === FormResponseMode.Multirespondent &&
enableMrfWebhooks))
diff --git a/apps/frontend/src/features/admin-form/settings/components/WebhooksSection/WebhooksErrorMsg.tsx b/apps/frontend/src/features/admin-form/settings/components/WebhooksSection/WebhooksErrorMsg.tsx
new file mode 100644
index 0000000000..411de32705
--- /dev/null
+++ b/apps/frontend/src/features/admin-form/settings/components/WebhooksSection/WebhooksErrorMsg.tsx
@@ -0,0 +1,41 @@
+import { useTranslation } from 'react-i18next'
+import { Flex, Text } from '@chakra-ui/react'
+
+import Button from '~components/Button'
+
+export interface WebhooksErrorMsgProps {
+ onRetry: () => void
+ isRetrying?: boolean
+}
+
+export const WebhooksErrorMsg = ({
+ onRetry,
+ isRetrying = false,
+}: WebhooksErrorMsgProps): JSX.Element => {
+ const { t } = useTranslation()
+ const { title, body, button } = t(
+ 'features.adminForm.settings.webhooks.error',
+ {
+ returnObjects: true,
+ },
+ )
+ return (
+
+
+ {title}
+
+
+ {body}
+
+
+
+
+
+ )
+}
diff --git a/apps/frontend/src/i18n/locales/features/admin-form/settings/webhooks/en-sg.ts b/apps/frontend/src/i18n/locales/features/admin-form/settings/webhooks/en-sg.ts
index 89daa00739..4ac0b70eb9 100644
--- a/apps/frontend/src/i18n/locales/features/admin-form/settings/webhooks/en-sg.ts
+++ b/apps/frontend/src/i18n/locales/features/admin-form/settings/webhooks/en-sg.ts
@@ -9,4 +9,12 @@ export const enSG = {
label: 'Enable retries',
description: `Your system must meet certain requirements before retries can be safely enabled. [Learn more]({url})`,
},
+ error: {
+ title: "Couldn't load webhook settings",
+ body: "Something went wrong while loading this form's settings. This does not affect your form or its responses. Please try again.",
+ button: {
+ label: 'Try again',
+ loadingText: 'Trying again…',
+ },
+ },
}
diff --git a/apps/frontend/src/i18n/locales/features/admin-form/settings/webhooks/index.ts b/apps/frontend/src/i18n/locales/features/admin-form/settings/webhooks/index.ts
index 67d797b927..e5583d2151 100644
--- a/apps/frontend/src/i18n/locales/features/admin-form/settings/webhooks/index.ts
+++ b/apps/frontend/src/i18n/locales/features/admin-form/settings/webhooks/index.ts
@@ -11,4 +11,12 @@ export interface Webhooks extends HasTitle {
label: string
description: string
}
+ error: {
+ title: string
+ body: string
+ button: {
+ label: string
+ loadingText: string
+ }
+ }
}