diff --git a/services/web/app/src/router.js b/services/web/app/src/router.js
index 90739bc5e3..2794bfca65 100644
--- a/services/web/app/src/router.js
+++ b/services/web/app/src/router.js
@@ -187,7 +187,7 @@ const rateLimiters = {
duration: 60,
}),
resendConfirmation: new RateLimiter('resend-confirmation', {
- points: 10,
+ points: 1,
duration: 60,
}),
sendChatMessage: new RateLimiter('send-chat-message', {
diff --git a/services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirm-affiliation.tsx b/services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirm-affiliation.tsx
index cec2e6732b..306763b087 100644
--- a/services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirm-affiliation.tsx
+++ b/services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirm-affiliation.tsx
@@ -4,7 +4,10 @@ import { Button } from 'react-bootstrap'
import Icon from '../../../../../../shared/components/icon'
import getMeta from '../../../../../../utils/meta'
import useAsync from '../../../../../../shared/hooks/use-async'
-import { postJSON } from '../../../../../../infrastructure/fetch-json'
+import {
+ FetchError,
+ postJSON,
+} from '../../../../../../infrastructure/fetch-json'
import { UserEmailData } from '../../../../../../../../types/user-email'
import { ExposedSettings } from '../../../../../../../../types/exposed-settings'
import { Institution } from '../../../../../../../../types/institution'
@@ -21,7 +24,7 @@ function ReconfirmAffiliation({
}: ReconfirmAffiliationProps) {
const { t } = useTranslation()
const { samlInitPath } = getMeta('ol-ExposedSettings') as ExposedSettings
- const { isLoading, isError, isSuccess, runAsync } = useAsync()
+ const { error, isLoading, isError, isSuccess, runAsync } = useAsync()
const [hasSent, setHasSent] = useState(false)
const [isPending, setIsPending] = useState(false)
const ssoEnabled = institution.ssoEnabled
@@ -48,6 +51,9 @@ function ReconfirmAffiliation({
}
}
+ const rateLimited =
+ error && error instanceof FetchError && error.response?.status === 429
+
if (hasSent) {
return (
@@ -73,7 +79,11 @@ function ReconfirmAffiliation({
{isError && (
<>
-
{t('generic_something_went_wrong')}
+
+ {rateLimited
+ ? t('too_many_requests')
+ : t('generic_something_went_wrong')}
+
>
)}
@@ -116,7 +126,11 @@ function ReconfirmAffiliation({
{isError && (
<>
- {t('generic_something_went_wrong')}
+
+ {rateLimited
+ ? t('too_many_requests')
+ : t('generic_something_went_wrong')}
+
>
)}
diff --git a/services/web/frontend/js/features/settings/components/emails/reconfirmation-info/reconfirmation-info-prompt.tsx b/services/web/frontend/js/features/settings/components/emails/reconfirmation-info/reconfirmation-info-prompt.tsx
index b761bc9fce..773a776ff4 100644
--- a/services/web/frontend/js/features/settings/components/emails/reconfirmation-info/reconfirmation-info-prompt.tsx
+++ b/services/web/frontend/js/features/settings/components/emails/reconfirmation-info/reconfirmation-info-prompt.tsx
@@ -1,6 +1,6 @@
import { useState, useEffect, useLayoutEffect } from 'react'
import useAsync from '../../../../../shared/hooks/use-async'
-import { postJSON } from '../../../../../infrastructure/fetch-json'
+import { FetchError, postJSON } from '../../../../../infrastructure/fetch-json'
import { Trans, useTranslation } from 'react-i18next'
import { Institution } from '../../../../../../../types/institution'
import { Button } from 'react-bootstrap'
@@ -24,7 +24,7 @@ function ReconfirmationInfoPrompt({
}: ReconfirmationInfoPromptProps) {
const { t } = useTranslation()
const { samlInitPath } = getMeta('ol-ExposedSettings') as ExposedSettings
- const { isLoading, isError, isSuccess, runAsync } = useAsync()
+ const { error, isLoading, isError, isSuccess, runAsync } = useAsync()
const { state, setLoading: setUserEmailsContextLoading } =
useUserEmailsContext()
const [isPending, setIsPending] = useState(false)
@@ -59,6 +59,9 @@ function ReconfirmationInfoPrompt({
}
}
+ const rateLimited =
+ isError && error instanceof FetchError && error.response?.status === 429
+
if (hasSent) {
return (
@@ -87,7 +90,11 @@ function ReconfirmationInfoPrompt({
)}
{isError && (
-
{t('generic_something_went_wrong')}
+
+ {rateLimited
+ ? t('too_many_requests')
+ : t('generic_something_went_wrong')}
+
)}
)
@@ -117,7 +124,11 @@ function ReconfirmationInfoPrompt({
{isError && (
- {t('generic_something_went_wrong')}
+
+ {rateLimited
+ ? t('too_many_requests')
+ : t('generic_something_went_wrong')}
+
)}
>
diff --git a/services/web/frontend/js/features/settings/components/emails/resend-confirmation-email-button.tsx b/services/web/frontend/js/features/settings/components/emails/resend-confirmation-email-button.tsx
index cb35cc20c6..fad75ba7e8 100644
--- a/services/web/frontend/js/features/settings/components/emails/resend-confirmation-email-button.tsx
+++ b/services/web/frontend/js/features/settings/components/emails/resend-confirmation-email-button.tsx
@@ -2,7 +2,7 @@ import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import Icon from '../../../../shared/components/icon'
import { Button } from 'react-bootstrap'
-import { postJSON } from '../../../../infrastructure/fetch-json'
+import { FetchError, postJSON } from '../../../../infrastructure/fetch-json'
import useAsync from '../../../../shared/hooks/use-async'
import { UserEmailData } from '../../../../../../types/user-email'
import { useUserEmailsContext } from '../../context/user-email-context'
@@ -15,7 +15,7 @@ function ResendConfirmationEmailButton({
email,
}: ResendConfirmationEmailButtonProps) {
const { t } = useTranslation()
- const { isLoading, isError, runAsync } = useAsync()
+ const { error, isLoading, isError, runAsync } = useAsync()
const { state, setLoading: setUserEmailsContextLoading } =
useUserEmailsContext()
@@ -42,6 +42,9 @@ function ResendConfirmationEmailButton({
)
}
+ const rateLimited =
+ error && error instanceof FetchError && error.response?.status === 429
+
return (
<>