Implement 'Add Affiliation' UI on the mobile version of the new react dashboard (#11606)

* Refactor & change `add-affiliation.tsx` file:
    * Change class from `user-profile` to `add-affiliation`
    * Add new optional `className` props for custom styling

* Show affiliation widget on mobile screen:

* Fix bug on use add affiliation hooks: return type should be `boolean`, not `boolean | 0`

GitOrigin-RevId: 800b951e2a012797266b1780993b4ed9a918d565
This commit is contained in:
M Fahru 2023-02-07 10:10:21 -07:00 committed by Copybot
parent de97ef98c0
commit 5f3ef71e43
9 changed files with 54 additions and 16 deletions

View file

@ -127,7 +127,7 @@ if (isOverleaf)
.row-spaced#userProfileInformation(ng-if="hasProjects")
div(ng-hide="withAffiliations", ng-cloak)
hr
.text-centered.user-profile
.text-centered.add-affiliation
p Are you affiliated with an institution?
a.btn.btn-secondary-info.btn-secondary(

View file

@ -1,19 +1,26 @@
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'
import { useProjectListContext } from '../context/project-list-context'
import getMeta from '../../../utils/meta'
import { Affiliation } from '../../../../../types/affiliation'
import { ExposedSettings } from '../../../../../types/exposed-settings'
import classNames from 'classnames'
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 }
return {
show: isOverleaf && totalProjectsCount > 0 && !userAffiliations.length,
}
}
function AddAffiliation() {
type AddAffiliationProps = {
className?: string
}
function AddAffiliation({ className }: AddAffiliationProps) {
const { t } = useTranslation()
const { show } = useAddAffiliation()
@ -21,8 +28,10 @@ function AddAffiliation() {
return null
}
const classes = classNames('text-centered', 'add-affiliation', className)
return (
<div className="text-centered user-profile">
<div className={classes}>
<p>{t('are_you_affiliated_with_an_institution')}</p>
<Button
bsStyle={null}

View file

@ -8,6 +8,7 @@ import getMeta from '../../../utils/meta'
import NewProjectButtonModal, {
NewProjectButtonModalVariant,
} from './new-project-button/new-project-button-modal'
import AddAffiliation, { useAddAffiliation } from './add-affiliation'
import { Nullable } from '../../../../../types/utils'
import { sendMB } from '../../../infrastructure/event-tracking'
@ -32,6 +33,7 @@ type NewProjectButtonProps = {
className?: string
menuClassName?: string
trackingKey?: string
showAddAffiliationWidget?: boolean
}
function NewProjectButton({
@ -40,12 +42,14 @@ function NewProjectButton({
className,
menuClassName,
trackingKey,
showAddAffiliationWidget,
}: NewProjectButtonProps) {
const { t } = useTranslation()
const { templateLinks } = getMeta('ol-ExposedSettings') as ExposedSettings
const [modal, setModal] =
useState<Nullable<NewProjectButtonModalVariant>>(null)
const portalTemplates = getMeta('ol-portalTemplates') as PortalTemplate[]
const { show: enableAddAffiliationWidget } = useAddAffiliation()
const sendTrackingEvent = useCallback(
({
@ -223,6 +227,14 @@ function NewProjectButton({
: templateLink.name}
</MenuItem>
))}
{showAddAffiliationWidget && enableAddAffiliationWidget ? (
<>
<MenuItem divider />
<li className="add-affiliation-mobile-wrapper">
<AddAffiliation className="is-mobile" />
</li>
</>
) : null}
</Dropdown.Menu>
</ControlledDropdown>
<NewProjectButtonModal modal={modal} onHide={() => setModal(null)} />

View file

@ -104,6 +104,7 @@ function ProjectListPageContent() {
<NewProjectButton
id="new-project-button-projects-table"
className="pull-left me-2"
showAddAffiliationWidget
/>
<SearchForm
inputValue={searchText}

View file

@ -1,6 +1,6 @@
import NewProjectButton from '../new-project-button'
import SidebarFilters from './sidebar-filters'
import AddAffiliation, { useAddAffiliation } from './add-affiliation'
import AddAffiliation, { useAddAffiliation } from '../add-affiliation'
import { usePersistedResize } from '../../../../shared/hooks/use-resize'
function Sidebar() {

View file

@ -1,4 +1,4 @@
import AddAffiliation from '../../js/features/project-list/components/sidebar/add-affiliation'
import AddAffiliation from '../../js/features/project-list/components/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'

View file

@ -217,7 +217,11 @@ input.project-list-table-select-item[type='checkbox'] {
text-align: center;
}
.user-profile {
.add-affiliation-mobile-wrapper {
padding: @padding-md 0;
}
.add-affiliation {
.progress {
height: @line-height-computed / 2;
margin-bottom: @line-height-computed / 4;
@ -225,6 +229,10 @@ input.project-list-table-select-item[type='checkbox'] {
p {
margin-bottom: @line-height-computed / 4;
}
&.is-mobile p {
font-size: 12px;
white-space: normal;
}
}
.user-notifications {

View file

@ -1,8 +1,14 @@
import { render, fireEvent, screen } from '@testing-library/react'
import { fireEvent, screen } from '@testing-library/react'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import NewProjectButton from '../../../../../frontend/js/features/project-list/components/new-project-button'
import { renderWithProjectListContext } from '../helpers/render-with-context'
describe('<NewProjectButton />', function () {
beforeEach(function () {
fetchMock.reset()
})
describe('for every user (affiliated and non-affiliated)', function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-ExposedSettings', {
@ -18,7 +24,7 @@ describe('<NewProjectButton />', function () {
],
})
render(<NewProjectButton id="test" />)
renderWithProjectListContext(<NewProjectButton id="test" />)
const newProjectButton = screen.getByRole('button', {
name: 'New Project',
@ -100,7 +106,7 @@ describe('<NewProjectButton />', function () {
})
it('shows the correct dropdown menu', function () {
render(<NewProjectButton id="test" />)
renderWithProjectListContext(<NewProjectButton id="test" />)
const newProjectButton = screen.getByRole('button', {
name: 'New Project',

View file

@ -2,7 +2,7 @@ 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 AddAffiliation from '../../../../../../frontend/js/features/project-list/components/add-affiliation'
import { Affiliation } from '../../../../../../types/affiliation'
describe('Add affiliation widget', function () {
@ -54,7 +54,9 @@ describe('Add affiliation widget', function () {
window.metaAttributesCache.set('ol-ExposedSettings', { isOverleaf: true })
window.metaAttributesCache.set('ol-userAffiliations', [])
renderWithProjectListContext(<AddAffiliation />, { projects: [] })
renderWithProjectListContext(<AddAffiliation />, {
projects: [],
})
await fetchMock.flush(true)
await waitFor(() => expect(fetchMock.called('/api/project')))