void
linkPath: string
+ disabled?: boolean
}
function ActionButton({
@@ -95,6 +99,7 @@ function ActionButton({
linked,
handleUnlinkClick,
linkPath,
+ disabled,
}: ActionButtonProps) {
const { t } = useTranslation()
@@ -106,13 +111,21 @@ function ActionButton({
)
} else if (linked) {
return (
-
- {errorMessage &&
{errorMessage}
}
+ {errorMessage ? (
+
+ ) : null}
+
+ {description}
+
+ )
+}
+
+type StatusIconProps = {
+ status: Status
+}
+
+function StatusIcon({ status }: StatusIconProps) {
+ switch (status) {
+ case 'success':
+ return (
+
+ )
+ case 'error':
+ return (
+
+ )
+ case 'pending':
+ return (
+
+ )
+ default:
+ return null
+ }
+}
diff --git a/services/web/frontend/js/features/settings/components/root.tsx b/services/web/frontend/js/features/settings/components/root.tsx
index ecd913a8e4..856ac4ab15 100644
--- a/services/web/frontend/js/features/settings/components/root.tsx
+++ b/services/web/frontend/js/features/settings/components/root.tsx
@@ -1,5 +1,4 @@
import { useEffect } from 'react'
-import { Alert } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import getMeta from '../../../utils/meta'
import EmailsSection from './emails-section'
@@ -38,16 +37,10 @@ function SettingsPageRoot() {
function SettingsPageContent() {
const { t } = useTranslation()
- const ssoError = getMeta('ol-ssoError') as string
const { isOverleaf } = getMeta('ol-ExposedSettings') as ExposedSettings
return (
- {ssoError ? (
-
- {t('sso_link_error')}: {t(ssoError)}
-
- ) : null}
{t('account_settings')}
diff --git a/services/web/frontend/stories/settings/helpers/linking.js b/services/web/frontend/stories/settings/helpers/linking.js
index 410f43a956..9400dbea1c 100644
--- a/services/web/frontend/stories/settings/helpers/linking.js
+++ b/services/web/frontend/stories/settings/helpers/linking.js
@@ -58,4 +58,5 @@ export function setDefaultMeta() {
})
window.metaAttributesCache.delete('integrationLinkingWidgets')
window.metaAttributesCache.delete('referenceLinkingWidgets')
+ window.metaAttributesCache.delete('ol-ssoErrorMessage')
}
diff --git a/services/web/frontend/stories/settings/linking.stories.js b/services/web/frontend/stories/settings/linking.stories.js
index 6c45a33e91..391295c0d1 100644
--- a/services/web/frontend/stories/settings/linking.stories.js
+++ b/services/web/frontend/stories/settings/linking.stories.js
@@ -4,6 +4,8 @@ import { setDefaultMeta, defaultSetupMocks } from './helpers/linking'
import { UserProvider } from '../../js/shared/context/user-context'
import { SSOProvider } from '../../js/features/settings/context/sso-context'
+const MOCK_DELAY = 1000
+
export const Section = args => {
useFetchMock(defaultSetupMocks)
setDefaultMeta()
@@ -40,6 +42,27 @@ export const SectionAllUnlinked = args => {
)
}
+export const SectionSSOErrors = args => {
+ useFetchMock(fetchMock =>
+ fetchMock.post('/user/oauth-unlink', 500, { delay: MOCK_DELAY })
+ )
+ setDefaultMeta()
+ window.metaAttributesCache.set('integrationLinkingWidgets', [])
+ window.metaAttributesCache.set('referenceLinkingWidgets', [])
+ window.metaAttributesCache.set(
+ 'ol-ssoErrorMessage',
+ 'Account already linked to another Overleaf user'
+ )
+
+ return (
+
+
+
+
+
+ )
+}
+
export default {
title: 'Account Settings / Linking',
component: LinkingSection,
diff --git a/services/web/frontend/stylesheets/app/account-settings.less b/services/web/frontend/stylesheets/app/account-settings.less
index b757e0c342..8c3b4a3778 100644
--- a/services/web/frontend/stylesheets/app/account-settings.less
+++ b/services/web/frontend/stylesheets/app/account-settings.less
@@ -107,17 +107,21 @@ tbody > tr.affiliations-table-warning-row > td {
margin-bottom: (@line-height-computed / 2) - @table-cell-padding;
}
+.settings-widget-status-icon,
.dropbox-sync-icon {
position: relative;
font-size: 1.3em;
line-height: 1.3em;
vertical-align: top;
+ &.status-error,
&.dropbox-sync-icon-error {
color: @alert-danger-bg;
}
+ &.status-success,
&.dropbox-sync-icon-success {
color: @alert-success-bg;
}
+ &.status-pending,
&.dropbox-sync-icon-updating {
color: @alert-info-bg;
&::after {
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 690e9bd695..437f50763d 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -635,7 +635,7 @@
"tracked_change_deleted": "Deleted",
"show_all": "show all",
"show_less": "show less",
- "dropbox_sync_error": "Dropbox Sync Error",
+ "dropbox_sync_error": "Sorry, there was an error talking to our Dropbox service. Please try again in a few moments.",
"send": "Send",
"sending": "Sending",
"invalid_password": "Invalid Password",
@@ -762,7 +762,7 @@
"mendeley_reference_loading_error_forbidden": "Could not load references from Mendeley, please re-link your account and try again",
"zotero_reference_loading_error_forbidden": "Could not load references from Zotero, please re-link your account and try again",
"mendeley_integration": "Mendeley Integration",
- "mendeley_sync_description": "With Mendeley integration you can import your references from Mendeley into your __appName__ projects.",
+ "mendeley_sync_description": "With the Mendeley integration you can import your references from Mendeley into your __appName__ projects.",
"mendeley_is_premium": "Mendeley integration is a premium feature",
"link_to_mendeley": "Link to Mendeley",
"unlink_to_mendeley": "Unlink Mendeley",
@@ -772,7 +772,7 @@
"mendeley_groups_loading_error": "There was an error loading groups from Mendeley",
"mendeley_groups_relink": "There was an error accessing your Mendeley data. This was likely caused by lack of permissions. Please re-link your account and try again.",
"zotero_integration": "Zotero Integration",
- "zotero_sync_description": "With Zotero integration you can import your references from Zotero into your __appName__ projects.",
+ "zotero_sync_description": "With the Zotero integration you can import your references from Zotero into your __appName__ projects.",
"zotero_is_premium": "Zotero integration is a premium feature",
"link_to_zotero": "Link to Zotero",
"unlink_to_zotero": "Unlink Zotero",
@@ -1463,8 +1463,8 @@
"about_us": "About Us",
"loading_recent_github_commits": "Loading recent commits",
"no_new_commits_in_github": "No new commits in GitHub since last merge.",
- "dropbox_sync_description": "Keep your __appName__ projects in sync with your Dropbox. Changes in __appName__ are automatically sent to your Dropbox, and the other way around.",
- "github_sync_description": "With GitHub Sync you can link your __appName__ projects to GitHub repositories. Create new commits from __appName__, and merge with commits made offline or in GitHub.",
+ "dropbox_sync_description": "Keep your __appName__ projects in sync with your Dropbox account. Changes in __appName__ are automatically sent to your Dropbox account, and the other way around.",
+ "github_sync_description": "With GitHub Sync you can link your __appName__ projects to GitHub repositories, create new commits from __appName__, and merge commits from GitHub.",
"github_import_description": "With GitHub Sync you can import your GitHub Repositories into __appName__. Create new commits from __appName__, and merge with commits made offline or in GitHub.",
"link_to_github_description": "You need to authorise __appName__ to access your GitHub account to allow us to sync your projects.",
"link": "Link",
diff --git a/services/web/test/frontend/features/settings/components/linking-section.test.tsx b/services/web/test/frontend/features/settings/components/linking-section.test.tsx
index 1492a2d75e..b70f19fa15 100644
--- a/services/web/test/frontend/features/settings/components/linking-section.test.tsx
+++ b/services/web/test/frontend/features/settings/components/linking-section.test.tsx
@@ -89,6 +89,12 @@ describe('', function () {
expect(screen.queryByText('Twitter')).to.not.exist
})
+ it('shows SSO error message', async function () {
+ window.metaAttributesCache.set('ol-ssoErrorMessage', 'You no SSO')
+ renderSectionWithProviders()
+ screen.getByText('Error linking SSO account: You no SSO')
+ })
+
it('does not show providers section when empty', async function () {
window.metaAttributesCache.delete('ol-oauthProviders')
renderSectionWithProviders()
diff --git a/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx b/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx
index 4d3cdc678d..e32bf81ec8 100644
--- a/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx
+++ b/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx
@@ -1,6 +1,7 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { screen, fireEvent, render, waitFor } from '@testing-library/react'
+import { FetchError } from '../../../../../../frontend/js/infrastructure/fetch-json'
import { SSOLinkingWidget } from '../../../../../../frontend/js/features/settings/components/linking/sso-widget'
describe('', function () {
@@ -103,7 +104,9 @@ describe('', function () {
describe('when unlinking fails', function () {
beforeEach(function () {
- const unlinkFunction = sinon.stub().rejects(new Error('unlinking failed'))
+ const unlinkFunction = sinon
+ .stub()
+ .rejects(new FetchError('unlinking failed', ''))
render(
)
@@ -116,7 +119,11 @@ describe('', function () {
})
it('should display an error message ', async function () {
- await waitFor(() => screen.getByText('unlinking failed'))
+ await waitFor(() =>
+ screen.getByText(
+ 'Something went wrong talking to the server :(. Please try again.'
+ )
+ )
})
it('should display the unlink button ', async function () {