Explorar o código

Merge pull request #7723 from overleaf/ta-settings-refactor

[SettingsPage] Refactor Linking Section

GitOrigin-RevId: 49aa27cdcb3669c59c9a9c46edd3249cee876dd0
Timothée Alby %!s(int64=4) %!d(string=hai) anos
pai
achega
146a207fd1

+ 1 - 0
services/web/config/settings.defaults.js

@@ -774,6 +774,7 @@ module.exports = {
     sourceEditorExtensions: [],
     sourceEditorComponents: [],
     integrationLinkingWidgets: [],
+    referenceLinkingWidgets: [],
   },
 
   moduleImportSequence: ['launchpad', 'server-ce-scripts', 'user-activate'],

+ 6 - 3
services/web/frontend/js/features/settings/components/emails-section.tsx

@@ -69,9 +69,12 @@ function EmailsSection() {
   }
 
   return (
-    <UserEmailsProvider>
-      <EmailsSectionContent />
-    </UserEmailsProvider>
+    <>
+      <UserEmailsProvider>
+        <EmailsSectionContent />
+      </UserEmailsProvider>
+      <hr />
+    </>
   )
 }
 

+ 0 - 46
services/web/frontend/js/features/settings/components/integration-linking-section.tsx

@@ -1,46 +0,0 @@
-import { useTranslation } from 'react-i18next'
-import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
-const integrationLinkingWidgets = importOverleafModules(
-  'integrationLinkingWidgets'
-)
-
-function IntegrationLinkingSection() {
-  const { t } = useTranslation()
-
-  return (
-    <>
-      <h3>{t('integrations')}</h3>
-      <p>{t('linked_accounts_explained')}</p>
-      <div className="settings-widgets-container">
-        {integrationLinkingWidgets.map(
-          ({ import: importObject, path }, widgetIndex) => (
-            <IntegrationLinkingWidget
-              key={Object.keys(importObject)[0]}
-              ModuleComponent={Object.values(importObject)[0]}
-              isLast={widgetIndex === integrationLinkingWidgets.length - 1}
-            />
-          )
-        )}
-      </div>
-    </>
-  )
-}
-
-type IntegrationLinkingWidgetProps = {
-  ModuleComponent: any
-  isLast: boolean
-}
-
-function IntegrationLinkingWidget({
-  ModuleComponent,
-  isLast,
-}: IntegrationLinkingWidgetProps) {
-  return (
-    <>
-      <ModuleComponent />
-      {isLast ? null : <hr />}
-    </>
-  )
-}
-
-export default IntegrationLinkingSection

+ 135 - 0
services/web/frontend/js/features/settings/components/linking-section.tsx

@@ -0,0 +1,135 @@
+import { useTranslation } from 'react-i18next'
+import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
+import { useSSOContext, SSOSubscription } from '../context/sso-context'
+import { SSOLinkingWidget } from './linking/sso-widget'
+
+const integrationLinkingWidgets = importOverleafModules(
+  'integrationLinkingWidgets'
+)
+const referenceLinkingWidgets = importOverleafModules('referenceLinkingWidgets')
+
+function LinkingSection() {
+  const { t } = useTranslation()
+  const { subscriptions } = useSSOContext()
+
+  const hasIntegrationLinkingSection = integrationLinkingWidgets.length
+  const hasReferencesLinkingSection = referenceLinkingWidgets.length
+  const hasSSOLinkingSection = Object.keys(subscriptions).length > 0
+
+  if (
+    !hasIntegrationLinkingSection &&
+    !hasReferencesLinkingSection &&
+    !hasSSOLinkingSection
+  ) {
+    return null
+  }
+
+  return (
+    <>
+      <h3>{t('integrations')}</h3>
+      <p>{t('linked_accounts_explained')}</p>
+      {hasIntegrationLinkingSection ? (
+        <>
+          <h3 className="text-capitalize">{t('sync_dropbox_github')}</h3>
+          <div className="settings-widgets-container">
+            {integrationLinkingWidgets.map(
+              ({ import: importObject, path }, widgetIndex) => (
+                <ModuleLinkingWidget
+                  key={Object.keys(importObject)[0]}
+                  ModuleComponent={Object.values(importObject)[0]}
+                  isLast={widgetIndex === integrationLinkingWidgets.length - 1}
+                />
+              )
+            )}
+          </div>
+        </>
+      ) : null}
+      {hasReferencesLinkingSection ? (
+        <>
+          <h3 className="text-capitalize">{t('reference_sync')}</h3>
+          <div className="settings-widgets-container">
+            {referenceLinkingWidgets.map(
+              ({ import: importObject, path }, widgetIndex) => (
+                <ModuleLinkingWidget
+                  key={Object.keys(importObject)[0]}
+                  ModuleComponent={Object.values(importObject)[0]}
+                  isLast={widgetIndex === referenceLinkingWidgets.length - 1}
+                />
+              )
+            )}
+          </div>
+        </>
+      ) : null}
+      {hasSSOLinkingSection ? (
+        <>
+          <h3 className="text-capitalize">{t('linked_accounts')}</h3>
+          <div className="settings-widgets-container">
+            {Object.values(subscriptions).map(
+              (subscription, subscriptionIndex) => (
+                <SSOLinkingWidgetContainer
+                  key={subscription.providerId}
+                  subscription={subscription}
+                  isLast={
+                    subscriptionIndex === Object.keys(subscriptions).length - 1
+                  }
+                />
+              )
+            )}
+          </div>
+        </>
+      ) : null}
+      {hasIntegrationLinkingSection ||
+      hasReferencesLinkingSection ||
+      hasSSOLinkingSection ? (
+        <hr />
+      ) : null}
+    </>
+  )
+}
+
+type LinkingWidgetProps = {
+  ModuleComponent: any
+  isLast: boolean
+}
+
+function ModuleLinkingWidget({ ModuleComponent, isLast }: LinkingWidgetProps) {
+  return (
+    <>
+      <ModuleComponent />
+      {isLast ? null : <hr />}
+    </>
+  )
+}
+
+type SSOLinkingWidgetContainerProps = {
+  subscription: SSOSubscription
+  isLast: boolean
+}
+
+function SSOLinkingWidgetContainer({
+  subscription,
+  isLast,
+}: SSOLinkingWidgetContainerProps) {
+  const { t } = useTranslation()
+  const { unlink } = useSSOContext()
+
+  return (
+    <>
+      <SSOLinkingWidget
+        providerId={subscription.providerId}
+        title={subscription.provider.name}
+        description={t(
+          subscription.provider.descriptionKey,
+          subscription.provider.descriptionOptions
+        )}
+        helpPath={subscription.provider.descriptionOptions?.link}
+        linked={subscription.linked}
+        linkPath={subscription.provider.linkPath}
+        onUnlink={() => unlink(subscription.providerId)}
+      />
+      {isLast ? null : <hr />}
+    </>
+  )
+}
+
+export default LinkingSection

+ 0 - 0
services/web/frontend/js/features/settings/components/integration-linking/widget.tsx → services/web/frontend/js/features/settings/components/linking/integration-widget.tsx


+ 0 - 0
services/web/frontend/js/features/settings/components/sso-linking/widget.tsx → services/web/frontend/js/features/settings/components/linking/sso-widget.tsx


+ 6 - 7
services/web/frontend/js/features/settings/components/root.tsx

@@ -5,12 +5,12 @@ import getMeta from '../../../utils/meta'
 import EmailsSection from './emails-section'
 import AccountInfoSection from './account-info-section'
 import PasswordSection from './password-section'
-import IntegrationLinkingSection from './integration-linking-section'
-import SSOLinkingSection from './sso-linking-section'
+import LinkingSection from './linking-section'
 import MiscSection from './misc-section'
 import LeaveSection from './leave-section'
 import * as eventTracking from '../../../infrastructure/event-tracking'
 import { UserProvider } from '../../../shared/context/user-context'
+import { SSOProvider } from '../context/sso-context'
 
 function SettingsPageRoot() {
   const { t } = useTranslation()
@@ -34,7 +34,7 @@ function SettingsPageRoot() {
               <div className="page-header">
                 <h1>{t('account_settings')}</h1>
               </div>
-              <div className="account-settings">
+              <div>
                 <EmailsSection />
                 <div className="row">
                   <div className="col-md-5">
@@ -45,10 +45,9 @@ function SettingsPageRoot() {
                   </div>
                 </div>
                 <hr />
-                <IntegrationLinkingSection />
-                <hr />
-                <SSOLinkingSection />
-                <hr />
+                <SSOProvider>
+                  <LinkingSection />
+                </SSOProvider>
                 <MiscSection />
                 <hr />
                 <LeaveSection />

+ 0 - 68
services/web/frontend/js/features/settings/components/sso-linking-section.tsx

@@ -1,68 +0,0 @@
-import { useTranslation } from 'react-i18next'
-import {
-  SSOProvider,
-  useSSOContext,
-  SSOSubscription,
-} from '../context/sso-context'
-import { SSOLinkingWidget } from './sso-linking/widget'
-
-function SSOLinkingSection() {
-  const { t } = useTranslation()
-
-  return (
-    <SSOProvider>
-      <h3 className="text-capitalize">{t('linked_accounts')}</h3>
-      <p>{t('linked_accounts_explained')}</p>
-      <SSOLinkingWidgets />
-    </SSOProvider>
-  )
-}
-
-function SSOLinkingWidgets() {
-  const { subscriptions } = useSSOContext()
-
-  return (
-    <div className="settings-widgets-container">
-      {Object.values(subscriptions).map((subscription, subscriptionIndex) => (
-        <SSOLinkingWidgetContainer
-          key={subscription.providerId}
-          subscription={subscription}
-          isLast={subscriptionIndex === Object.keys(subscriptions).length - 1}
-        />
-      ))}
-    </div>
-  )
-}
-
-type SSOLinkingWidgetContainerProps = {
-  subscription: SSOSubscription
-  isLast: boolean
-}
-
-function SSOLinkingWidgetContainer({
-  subscription,
-  isLast,
-}: SSOLinkingWidgetContainerProps) {
-  const { t } = useTranslation()
-  const { unlink } = useSSOContext()
-
-  return (
-    <>
-      <SSOLinkingWidget
-        providerId={subscription.providerId}
-        title={subscription.provider.name}
-        description={t(
-          subscription.provider.descriptionKey,
-          subscription.provider.descriptionOptions
-        )}
-        helpPath={subscription.provider.descriptionOptions?.link}
-        linked={subscription.linked}
-        linkPath={subscription.provider.linkPath}
-        onUnlink={() => unlink(subscription.providerId)}
-      />
-      {isLast ? null : <hr />}
-    </>
-  )
-}
-
-export default SSOLinkingSection

+ 0 - 21
services/web/frontend/stories/settings/helpers/integration-linking.js

@@ -1,21 +0,0 @@
-const MOCK_DELAY = 1000
-
-export function defaultSetupMocks(fetchMock) {
-  fetchMock.get(
-    'express:/user/tpds/queues',
-    { tpdsToWeb: 0, webToTpds: 0 },
-    { delay: MOCK_DELAY }
-  )
-}
-
-export function setDefaultMeta() {
-  window.metaAttributesCache.set('ol-user', {
-    features: { github: true, dropbox: true, mendeley: false, zotero: false },
-    refProviders: {
-      mendeley: true,
-      zotero: true,
-    },
-  })
-  window.metaAttributesCache.set('ol-github', { enabled: false })
-  window.metaAttributesCache.set('ol-dropbox', { registered: true })
-}

+ 16 - 1
services/web/frontend/stories/settings/helpers/sso-linking.js → services/web/frontend/stories/settings/helpers/linking.js

@@ -1,10 +1,25 @@
 const MOCK_DELAY = 1000
 
 export function defaultSetupMocks(fetchMock) {
-  fetchMock.post('/user/oauth-unlink', 200, { delay: MOCK_DELAY })
+  fetchMock
+    .post('/user/oauth-unlink', 200, { delay: MOCK_DELAY })
+    .get(
+      'express:/user/tpds/queues',
+      { tpdsToWeb: 0, webToTpds: 0 },
+      { delay: MOCK_DELAY }
+    )
 }
 
 export function setDefaultMeta() {
+  window.metaAttributesCache.set('ol-user', {
+    features: { github: true, dropbox: true, mendeley: false, zotero: false },
+    refProviders: {
+      mendeley: true,
+      zotero: true,
+    },
+  })
+  window.metaAttributesCache.set('ol-github', { enabled: false })
+  window.metaAttributesCache.set('ol-dropbox', { registered: true })
   window.metaAttributesCache.set('ol-thirdPartyIds', {
     collabratec: 'collabratec-id',
     google: 'google-id',

+ 0 - 23
services/web/frontend/stories/settings/integration-linking.stories.js

@@ -1,23 +0,0 @@
-import useFetchMock from '../hooks/use-fetch-mock'
-import IntegrationLinkingSection from '../../js/features/settings/components/integration-linking-section'
-import {
-  setDefaultMeta,
-  defaultSetupMocks,
-} from './helpers/integration-linking'
-import { UserProvider } from '../../js/shared/context/user-context'
-
-export const Section = args => {
-  useFetchMock(defaultSetupMocks)
-  setDefaultMeta()
-
-  return (
-    <UserProvider>
-      <IntegrationLinkingSection {...args} />
-    </UserProvider>
-  )
-}
-
-export default {
-  title: 'Account Settings / Integration Linking / Section',
-  component: IntegrationLinkingSection,
-}

+ 46 - 0
services/web/frontend/stories/settings/linking.stories.js

@@ -0,0 +1,46 @@
+import useFetchMock from '../hooks/use-fetch-mock'
+import LinkingSection from '../../js/features/settings/components/linking-section'
+import { setDefaultMeta, defaultSetupMocks } from './helpers/linking'
+import { UserProvider } from '../../js/shared/context/user-context'
+import { SSOProvider } from '../../js/features/settings/context/sso-context'
+
+export const Section = args => {
+  useFetchMock(defaultSetupMocks)
+  setDefaultMeta()
+
+  return (
+    <UserProvider>
+      <SSOProvider>
+        <LinkingSection {...args} />
+      </SSOProvider>
+    </UserProvider>
+  )
+}
+
+export const SectionAllUnlinked = args => {
+  useFetchMock(defaultSetupMocks)
+  setDefaultMeta()
+  window.metaAttributesCache.set('ol-thirdPartyIds', {})
+  window.metaAttributesCache.set('ol-user', {
+    features: { github: true, dropbox: true, mendeley: true, zotero: true },
+    refProviders: {
+      mendeley: false,
+      zotero: false,
+    },
+  })
+  window.metaAttributesCache.set('ol-github', { enabled: false })
+  window.metaAttributesCache.set('ol-dropbox', { registered: false })
+
+  return (
+    <UserProvider>
+      <SSOProvider>
+        <LinkingSection {...args} />
+      </SSOProvider>
+    </UserProvider>
+  )
+}
+
+export default {
+  title: 'Account Settings / Linking',
+  component: LinkingSection,
+}

+ 5 - 11
services/web/frontend/stories/settings/page.stories.js

@@ -17,13 +17,9 @@ import {
   defaultSetupMocks as defaultSetupEmailsMocks,
 } from './helpers/emails'
 import {
-  setDefaultMeta as setDefaultIntegrationLinkingMeta,
-  defaultSetupMocks as defaultSetupIntegrationLinkingMocks,
-} from './helpers/integration-linking'
-import {
-  setDefaultMeta as setDefaultSSOMeta,
-  defaultSetupMocks as defaultSetupSSOMocks,
-} from './helpers/sso-linking'
+  setDefaultMeta as setDefaultLinkingMeta,
+  defaultSetupMocks as defaultSetupLinkingMocks,
+} from './helpers/linking'
 import { UserProvider } from '../../js/shared/context/user-context'
 
 export const Root = args => {
@@ -31,14 +27,12 @@ export const Root = args => {
   setDefaultAccountInfoMeta()
   setDefaultPasswordMeta()
   setDefaultEmailsMeta()
-  setDefaultIntegrationLinkingMeta()
-  setDefaultSSOMeta()
+  setDefaultLinkingMeta()
   useFetchMock(defaultSetupLeaveMocks)
   useFetchMock(defaultSetupAccountInfoMocks)
   useFetchMock(defaultSetupPasswordMocks)
   useFetchMock(defaultSetupEmailsMocks)
-  useFetchMock(defaultSetupIntegrationLinkingMocks)
-  useFetchMock(defaultSetupSSOMocks)
+  useFetchMock(defaultSetupLinkingMocks)
 
   return (
     <UserProvider>

+ 0 - 23
services/web/frontend/stories/settings/sso-linking.stories.js

@@ -1,23 +0,0 @@
-import useFetchMock from '../hooks/use-fetch-mock'
-import SSOLinkingSection from '../../js/features/settings/components/sso-linking-section'
-import { setDefaultMeta, defaultSetupMocks } from './helpers/sso-linking'
-
-export const Section = args => {
-  useFetchMock(defaultSetupMocks)
-  setDefaultMeta()
-
-  return <SSOLinkingSection {...args} />
-}
-
-export const SectionAllUnlinked = args => {
-  useFetchMock(defaultSetupMocks)
-  setDefaultMeta()
-  window.metaAttributesCache.set('ol-thirdPartyIds', {})
-
-  return <SSOLinkingSection {...args} />
-}
-
-export default {
-  title: 'Account Settings / SSO Linking / Section',
-  component: SSOLinkingSection,
-}

+ 1 - 1
services/web/test/frontend/features/settings/components/integration-linking/widget.test.tsx → services/web/test/frontend/features/settings/components/linking/integration-widget.test.tsx

@@ -1,6 +1,6 @@
 import { expect } from 'chai'
 import { screen, fireEvent, render, waitFor } from '@testing-library/react'
-import { IntegrationLinkingWidget } from '../../../../../../frontend/js/features/settings/components/integration-linking/widget'
+import { IntegrationLinkingWidget } from '../../../../../../frontend/js/features/settings/components/linking/integration-widget'
 
 describe('<IntegrationLinkingWidgetTest/>', function () {
   const defaultProps = {

+ 1 - 1
services/web/test/frontend/features/settings/components/sso-linking/widget.test.tsx → services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx

@@ -1,7 +1,7 @@
 import { expect } from 'chai'
 import sinon from 'sinon'
 import { screen, fireEvent, render, waitFor } from '@testing-library/react'
-import { SSOLinkingWidget } from '../../../../../../frontend/js/features/settings/components/sso-linking/widget'
+import { SSOLinkingWidget } from '../../../../../../frontend/js/features/settings/components/linking/sso-widget'
 
 describe('<SSOLinkingWidget />', function () {
   const defaultProps = {