Просмотр исходного кода

Merge pull request #10132 from overleaf/ii-dashboard-are-you-affiliated-migration

[web] Project dashboard are you affiliated migration

GitOrigin-RevId: 455ad915dd023c5fab3ce9a5165aa7e960915f1f
ilkin-overleaf 4 лет назад
Родитель
Сommit
7180ee8700

+ 1 - 0
services/web/app/src/Features/Project/ProjectListController.js

@@ -293,6 +293,7 @@ async function projectListReactPage(req, res, next) {
     notifications,
     notificationsInstitution,
     user,
+    userAffiliations,
     userEmails,
     reconfirmedViaSAML,
     allInReconfirmNotificationPeriods,

+ 1 - 0
services/web/app/views/project/list-react.pug

@@ -13,6 +13,7 @@ block append meta
 	meta(name="ol-userEmails" data-type="json" content=userEmails)
 	meta(name="ol-allInReconfirmNotificationPeriods" data-type="json" content=allInReconfirmNotificationPeriods)
 	meta(name="ol-user" data-type="json" content=user)
+	meta(name="ol-userAffiliations" data-type="json" content=userAffiliations)
 	meta(name="ol-reconfirmedViaSAML" content=reconfirmedViaSAML)
 	meta(name="ol-survey" data-type="json" content=survey)
 	meta(name="ol-tags" data-type="json" content=tags)

+ 1 - 0
services/web/frontend/extracted-translations.json

@@ -30,6 +30,7 @@
   "archived": "",
   "archived_projects": "",
   "archiving_projects_wont_affect_collaborators": "",
+  "are_you_affiliated_with_an_institution": "",
   "are_you_still_at": "",
   "ascending": "",
   "ask_proj_owner_to_upgrade_for_git_bridge": "",

+ 4 - 0
services/web/frontend/js/features/project-list/components/project-list-root.tsx

@@ -10,6 +10,7 @@ import CurrentPlanWidget from './current-plan-widget/current-plan-widget'
 import NewProjectButton from './new-project-button'
 import ProjectListTable from './table/project-list-table'
 import SidebarFilters from './sidebar/sidebar-filters'
+import AddAffiliation, { useAddAffiliation } from './sidebar/add-affiliation'
 import SurveyWidget from './survey-widget'
 import WelcomeMessage from './welcome-message'
 import LoadingBranded from '../../../shared/components/loading-branded'
@@ -42,6 +43,7 @@ function ProjectListPageContent() {
     setSearchText,
     selectedProjects,
   } = useProjectListContext()
+  const { show: showAddAffiliationWidget } = useAddAffiliation()
 
   useEffect(() => {
     eventTracking.sendMB('loads_v2_dash', {})
@@ -62,6 +64,8 @@ function ProjectListPageContent() {
                 <aside className="project-list-sidebar-react">
                   <NewProjectButton id="new-project-button-sidebar" />
                   <SidebarFilters />
+                  {showAddAffiliationWidget && <hr />}
+                  <AddAffiliation />
                 </aside>
               </div>
             </div>

+ 34 - 0
services/web/frontend/js/features/project-list/components/sidebar/add-affiliation.tsx

@@ -0,0 +1,34 @@
+import { useTranslation } from 'react-i18next'
+import { Button } from 'react-bootstrap'
+import { useProjectListContext } from '../../context/project-list-context'
+import getMeta from '../../../../utils/meta'
+import { Affiliation } from '../../../../../../types/affiliation'
+import { ExposedSettings } from '../../../../../../types/exposed-settings'
+
+export function useAddAffiliation() {
+  const { totalProjectsCount } = useProjectListContext()
+  const { isOverleaf } = getMeta('ol-ExposedSettings') as ExposedSettings
+  const userAffiliations = getMeta('ol-userAffiliations', []) as Affiliation[]
+
+  return { show: isOverleaf && totalProjectsCount && !userAffiliations.length }
+}
+
+function AddAffiliation() {
+  const { t } = useTranslation()
+  const { show } = useAddAffiliation()
+
+  if (!show) {
+    return null
+  }
+
+  return (
+    <div className="text-centered user-profile">
+      <p>{t('are_you_affiliated_with_an_institution')}</p>
+      <Button bsStyle="info" href="/user/settings">
+        {t('add_affiliation')}
+      </Button>
+    </div>
+  )
+}
+
+export default AddAffiliation

+ 29 - 0
services/web/frontend/stories/project-list/add-affiliation.stories.tsx

@@ -0,0 +1,29 @@
+import AddAffiliation from '../../js/features/project-list/components/sidebar/add-affiliation'
+import { ProjectListProvider } from '../../js/features/project-list/context/project-list-context'
+import useFetchMock from '../hooks/use-fetch-mock'
+import { projectsData } from '../../../test/frontend/features/project-list/fixtures/projects-data'
+
+export const Add = (args: any) => {
+  window.metaAttributesCache = new Map()
+  window.metaAttributesCache.set('ol-ExposedSettings', {
+    isOverleaf: true,
+  })
+  window.metaAttributesCache.set('ol-userAffiliations', [])
+  useFetchMock(fetchMock => {
+    fetchMock.post(/\/api\/project/, {
+      projects: projectsData,
+      totalSize: projectsData.length,
+    })
+  })
+
+  return (
+    <ProjectListProvider>
+      <AddAffiliation {...args} />
+    </ProjectListProvider>
+  )
+}
+
+export default {
+  title: 'Project List / Affiliation',
+  component: AddAffiliation,
+}

+ 1 - 0
services/web/locales/en.json

@@ -1584,6 +1584,7 @@
   "link_institutional_email_get_started": "Link an institutional email address to your account to get started.",
   "looks_like_youre_at": "It looks like you’re at <0>__institutionName__</0>!",
   "add_affiliation": "Add Affiliation",
+  "are_you_affiliated_with_an_institution": "Are you affiliated with an institution?",
   "did_you_know_institution_providing_professional": "Did you know that __institutionName__ is providing <0>free __appName__ Professional features</0> to everyone at __institutionName__?",
   "add_email_to_claim_features": "Add an institutional email address to claim your features.",
   "please_change_primary_to_remove": "Please change your primary email in order to remove",

+ 76 - 0
services/web/test/frontend/features/project-list/components/sidebar/add-affiliation.test.tsx

@@ -0,0 +1,76 @@
+import { screen, waitFor } from '@testing-library/react'
+import { expect } from 'chai'
+import fetchMock from 'fetch-mock'
+import { renderWithProjectListContext } from '../../helpers/render-with-context'
+import AddAffiliation from '../../../../../../frontend/js/features/project-list/components/sidebar/add-affiliation'
+import { Affiliation } from '../../../../../../types/affiliation'
+
+describe('Add affiliation widget', function () {
+  const validateNonExistence = () => {
+    expect(screen.queryByText(/are you affiliated with an institution/i)).to.be
+      .null
+    expect(screen.queryByRole('link', { name: /add affiliation/i })).to.be.null
+  }
+
+  beforeEach(function () {
+    window.metaAttributesCache = new Map()
+    fetchMock.reset()
+  })
+
+  afterEach(function () {
+    window.metaAttributesCache = new Map()
+    fetchMock.reset()
+  })
+
+  it('renders the component', async function () {
+    window.metaAttributesCache.set('ol-ExposedSettings', { isOverleaf: true })
+    window.metaAttributesCache.set('ol-userAffiliations', [])
+
+    renderWithProjectListContext(<AddAffiliation />)
+
+    await fetchMock.flush(true)
+    await waitFor(() => expect(fetchMock.called('/api/project')))
+
+    screen.getByText(/are you affiliated with an institution/i)
+    const addAffiliationLink = screen.getByRole('link', {
+      name: /add affiliation/i,
+    })
+    expect(addAffiliationLink.getAttribute('href')).to.equal('/user/settings')
+  })
+
+  it('does not render when `isOverleaf` is `false`', async function () {
+    window.metaAttributesCache.set('ol-ExposedSettings', { isOverleaf: false })
+    window.metaAttributesCache.set('ol-userAffiliations', [])
+
+    renderWithProjectListContext(<AddAffiliation />)
+
+    await fetchMock.flush(true)
+    await waitFor(() => expect(fetchMock.called('/api/project')))
+
+    validateNonExistence()
+  })
+
+  it('does not render when there no projects', async function () {
+    window.metaAttributesCache.set('ol-ExposedSettings', { isOverleaf: true })
+    window.metaAttributesCache.set('ol-userAffiliations', [])
+
+    renderWithProjectListContext(<AddAffiliation />, { projects: [] })
+
+    await fetchMock.flush(true)
+    await waitFor(() => expect(fetchMock.called('/api/project')))
+
+    validateNonExistence()
+  })
+
+  it('does not render when there are affiliations', async function () {
+    window.metaAttributesCache.set('ol-ExposedSettings', { isOverleaf: true })
+    window.metaAttributesCache.set('ol-userAffiliations', [{} as Affiliation])
+
+    renderWithProjectListContext(<AddAffiliation />)
+
+    await fetchMock.flush(true)
+    await waitFor(() => expect(fetchMock.called('/api/project')))
+
+    validateNonExistence()
+  })
+})