[web] Tear down sidebar-navigation-ui-update, Update project-list look in SP/CE (#24920)

* Remove hacks that conditionally hid `ds-nav` survey

* Remove `getAssignment` of `sidebar-navigation-ui-update`

* Remove `hasDsNav`: make it true everywhere

* Remove dead code

* Update Footer so thin footer is shown in SP/CE

* Run `web$ make cleanup_unused_locales` & `bin/run web npm run extract-translations`

* [server-pro] fix learn wiki tests following DS navigation changes

* [server-pro] tests: remove logout action before switching session

* [server-pro] tests: fix logout test

* [server-pro] tests: use new css class for sidebar on project dashboard

* Revert "should add a documentation entry to the nav bar" test change

---------

Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>
GitOrigin-RevId: 93eb7a1b03bb4e54ad1770150d83778b8f7f6727
This commit is contained in:
Antoine Clausse
2025-04-24 16:56:28 +02:00
committed by Copybot
parent cf36767f03
commit 13270dee2d
15 changed files with 23 additions and 216 deletions

View File

@@ -9,7 +9,7 @@ describe('Accounts', function () {
it('can log in and out', function () {
login('user@example.com')
cy.visit('/project')
cy.findByText('Account').click()
cy.findByRole('menuitem', { name: 'Account' }).click()
cy.findByText('Log Out').click()
cy.url().should('include', '/login')
cy.visit('/project')

View File

@@ -293,7 +293,7 @@ describe('admin panel', function () {
cy.findByText(deletedProjectName).should('not.exist')
cy.log('navigate to thrashed projects and delete the project')
cy.get('.project-list-sidebar-react').within(() => {
cy.get('.project-list-sidebar-scroll').within(() => {
cy.findByText('Trashed Projects').click()
})
findProjectRow(deletedProjectName).within(() =>
@@ -318,7 +318,7 @@ describe('admin panel', function () {
cy.log('login as the user and verify the project is restored')
login(user1)
cy.visit('/project')
cy.get('.project-list-sidebar-react').within(() => {
cy.get('.project-list-sidebar-scroll').within(() => {
cy.findByText('Trashed Projects').click()
})
cy.findByText(`${deletedProjectName} (Restored)`)

View File

@@ -102,10 +102,6 @@ describe('Project creation and compilation', function () {
cy.findByText('Invite not yet accepted.')
})
cy.visit('/project')
cy.findByText('Account').click()
cy.findByText('Log Out').click()
login('collaborator@example.com')
openProjectViaInviteNotification(targetProjectName)
cy.get('@targetProjectId').then(targetProjectId => {

View File

@@ -96,12 +96,12 @@ describe('Templates', () => {
.parent()
.parent()
.within(() => cy.get('input[type="checkbox"]').first().check())
cy.get('.project-list-sidebar-react').within(() => {
cy.get('.project-list-sidebar-scroll').within(() => {
cy.findAllByText('New Tag').first().click()
})
cy.focused().type(tagName)
cy.findByText('Create').click()
cy.get('.project-list-sidebar-react').within(() => {
cy.get('.project-list-sidebar-scroll').within(() => {
cy.findByText(tagName)
.parent()
.within(() => cy.get('.name').should('have.text', `${tagName} (1)`))

View File

@@ -411,15 +411,6 @@ async function projectListPage(req, res, next) {
logger.error({ err: error }, 'Failed to get individual subscription')
}
// Get the user's assignment for the DS unified nav split test, which
// populates splitTestVariants with a value for the split test name and allows
// Pug to send it to the browser
await SplitTestHandler.promises.getAssignment(
req,
res,
'sidebar-navigation-ui-update'
)
// Get the user's assignment for the papers notification banner split test,
// which populates splitTestVariants with a value for the split test name and
// allows Pug to send it to the browser

View File

@@ -1127,7 +1127,6 @@
"optional": "",
"or": "",
"organization_name": "",
"organize_projects": "",
"organize_tags": "",
"other": "",
"other_causes_of_compile_timeouts": "",

View File

@@ -1,119 +0,0 @@
import { useProjectListContext } from '../context/project-list-context'
import { useTranslation } from 'react-i18next'
import CurrentPlanWidget from './current-plan-widget/current-plan-widget'
import NewProjectButton from './new-project-button'
import ProjectListTable from './table/project-list-table'
import SurveyWidget from './survey-widget'
import UserNotifications from './notifications/user-notifications'
import SearchForm from './search-form'
import ProjectsDropdown from './dropdown/projects-dropdown'
import SortByDropdown from './dropdown/sort-by-dropdown'
import ProjectTools from './table/project-tools/project-tools'
import ProjectListTitle from './title/project-list-title'
import Sidebar from './sidebar/sidebar'
import LoadMore from './load-more'
import OLCol from '@/features/ui/components/ol/ol-col'
import OLRow from '@/features/ui/components/ol/ol-row'
import { TableContainer } from '@/features/ui/components/bootstrap-5/table'
import DashApiError from '@/features/project-list/components/dash-api-error'
export default function ProjectListDefault() {
const { t } = useTranslation()
const {
error,
searchText,
setSearchText,
selectedProjects,
filter,
tags,
selectedTagId,
} = useProjectListContext()
const selectedTag = tags.find(tag => tag._id === selectedTagId)
const tableTopArea = (
<div className="pt-2 pb-3 d-md-none d-flex gap-2">
<NewProjectButton
id="new-project-button-projects-table"
showAddAffiliationWidget
/>
<SearchForm
inputValue={searchText}
setInputValue={setSearchText}
filter={filter}
selectedTag={selectedTag}
className="overflow-hidden flex-grow-1"
/>
</div>
)
return (
<>
<Sidebar />
<div className="project-list-main-react">
{error ? <DashApiError /> : ''}
<OLRow>
<OLCol>
<UserNotifications />
</OLCol>
</OLRow>
<div className="project-list-header-row">
<ProjectListTitle
filter={filter}
selectedTag={selectedTag}
selectedTagId={selectedTagId}
className="text-truncate d-none d-md-block"
/>
<div className="project-tools">
<div className="d-none d-md-block">
{selectedProjects.length === 0 ? (
<CurrentPlanWidget />
) : (
<ProjectTools />
)}
</div>
<div className="d-md-none">
<CurrentPlanWidget />
</div>
</div>
</div>
<OLRow className="d-none d-md-block">
<OLCol lg={7}>
<SearchForm
inputValue={searchText}
setInputValue={setSearchText}
filter={filter}
selectedTag={selectedTag}
/>
</OLCol>
</OLRow>
<div className="project-list-sidebar-survey-wrapper d-md-none">
<SurveyWidget />
</div>
<div className="mt-1 d-md-none">
<div
role="toolbar"
className="projects-toolbar"
aria-label={t('projects')}
>
<ProjectsDropdown />
<SortByDropdown />
</div>
</div>
<OLRow className="row-spaced">
<OLCol>
<TableContainer bordered>
{tableTopArea}
<ProjectListTable />
</TableContainer>
</OLCol>
</OLRow>
<OLRow className="row-spaced">
<OLCol>
<LoadMore />
</OLCol>
</OLRow>
</div>
</>
)
}

View File

@@ -16,13 +16,14 @@ import { TableContainer } from '@/features/ui/components/bootstrap-5/table'
import DashApiError from '@/features/project-list/components/dash-api-error'
import getMeta from '@/utils/meta'
import DefaultNavbar from '@/features/ui/components/bootstrap-5/navbar/default-navbar'
import FatFooter from '@/features/ui/components/bootstrap-5/footer/fat-footer'
import Footer from '@/features/ui/components/bootstrap-5/footer/footer'
import SidebarDsNav from '@/features/project-list/components/sidebar/sidebar-ds-nav'
import SystemMessages from '@/shared/components/system-messages'
import overleafLogo from '@/shared/svgs/overleaf-a-ds-solution-mallard.svg'
export function ProjectListDsNav() {
const navbarProps = getMeta('ol-navbar')
const footerProps = getMeta('ol-footer')
const { t } = useTranslation()
const {
error,
@@ -117,7 +118,7 @@ export function ProjectListDsNav() {
</div>
</div>
</div>
<FatFooter />
<Footer {...footerProps} />
</div>
<div>
<SystemMessages />

View File

@@ -16,12 +16,8 @@ import getMeta from '@/utils/meta'
import DefaultNavbar from '@/features/ui/components/bootstrap-5/navbar/default-navbar'
import Footer from '@/features/ui/components/bootstrap-5/footer/footer'
import WelcomePageContent from '@/features/project-list/components/welcome-page-content'
import ProjectListDefault from '@/features/project-list/components/project-list-default'
import { ProjectListDsNav } from '@/features/project-list/components/project-list-ds-nav'
import {
DsNavStyleProvider,
hasDsNav,
} from '@/features/project-list/components/use-is-ds-nav'
import { DsNavStyleProvider } from '@/features/project-list/components/use-is-ds-nav'
function ProjectListRoot() {
const { isReady } = useWaitForI18n()
@@ -87,15 +83,7 @@ function ProjectListPageContent() {
<LoadingBranded loadProgress={loadProgress} label={t('loading')} />
)
if (hasDsNav()) {
return loadingComponent
} else {
return (
<DefaultNavbarAndFooter>
<div className="loading-container">{loadingComponent}</div>
</DefaultNavbarAndFooter>
)
}
return loadingComponent
}
if (totalProjectsCount === 0) {
@@ -104,19 +92,12 @@ function ProjectListPageContent() {
<WelcomePageContent />
</DefaultPageContentWrapper>
)
} else if (hasDsNav()) {
return (
<DsNavStyleProvider>
<ProjectListDsNav />
</DsNavStyleProvider>
)
} else {
return (
<DefaultPageContentWrapper>
<ProjectListDefault />
</DefaultPageContentWrapper>
)
}
return (
<DsNavStyleProvider>
<ProjectListDsNav />
</DsNavStyleProvider>
)
}
export default withErrorBoundary(ProjectListRoot, GenericErrorBoundaryFallback)

View File

@@ -5,7 +5,6 @@ import {
} from '../../context/project-list-context'
import TagsList from './tags-list'
import ProjectsFilterMenu from '../projects-filter-menu'
import { hasDsNav } from '@/features/project-list/components/use-is-ds-nav'
type SidebarFilterProps = {
filter: Filter
@@ -38,11 +37,9 @@ export default function SidebarFilters() {
<SidebarFilter filter="shared" text={t('shared_with_you')} />
<SidebarFilter filter="archived" text={t('archived_projects')} />
<SidebarFilter filter="trashed" text={t('trashed_projects')} />
{hasDsNav() && (
<li role="none">
<hr />
</li>
)}
<li role="none">
<hr />
</li>
<TagsList />
</ul>
)

View File

@@ -1,7 +1,6 @@
import { sortBy } from 'lodash'
import { useTranslation } from 'react-i18next'
import { DotsThreeVertical, Plus, TagSimple } from '@phosphor-icons/react'
import MaterialIcon from '../../../../shared/components/material-icon'
import {
UNCATEGORIZED_KEY,
useProjectListContext,
@@ -14,7 +13,6 @@ import {
DropdownMenu,
DropdownToggle,
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
import { hasDsNav } from '@/features/project-list/components/use-is-ds-nav'
export default function TagsList() {
const { t } = useTranslation()
@@ -42,16 +40,11 @@ export default function TagsList() {
aria-hidden="true"
data-testid="organize-projects"
>
{hasDsNav() ? t('organize_tags') : t('organize_projects')}
{t('organize_tags')}
</li>
<li className="tag">
<button type="button" className="tag-name" onClick={openCreateTagModal}>
{hasDsNav() ? (
<Plus weight="bold" />
) : (
<MaterialIcon type="add" className="tag-list-icon" />
)}
<Plus weight="bold" />
<span className="name">{t('new_tag')}</span>
</button>
</li>
@@ -73,11 +66,7 @@ export default function TagsList() {
color: getTagColor(tag),
}}
>
{hasDsNav() ? (
<TagSimple weight="fill" className="tag-list-icon" />
) : (
<MaterialIcon type="label" className="tag-list-icon" />
)}
<TagSimple weight="fill" className="tag-list-icon" />
</span>
<span className="name">
{tag.name}{' '}
@@ -93,7 +82,7 @@ export default function TagsList() {
id={`${tag._id}-dropdown-toggle`}
data-testid="tag-dropdown-toggle"
>
{hasDsNav() && <DotsThreeVertical weight="bold" />}
<DotsThreeVertical weight="bold" />
</DropdownToggle>
<DropdownMenu className="dropdown-menu-sm-width">
<DropdownItem

View File

@@ -1,11 +1,9 @@
import usePersistedState from '../../../shared/hooks/use-persisted-state'
import getMeta from '../../../utils/meta'
import { useCallback } from 'react'
import classnames from 'classnames'
import OLButton from '@/features/ui/components/ol/ol-button'
import { useTranslation } from 'react-i18next'
import { X } from '@phosphor-icons/react'
import { useHideDsSurvey } from '@/features/project-list/components/use-is-ds-nav'
export function SurveyWidgetDsNav() {
const { t } = useTranslation()
@@ -14,7 +12,6 @@ export function SurveyWidgetDsNav() {
`dismissed-${survey?.name}`,
false
)
const hideDsSurvey = useHideDsSurvey()
const dismissSurvey = useCallback(() => {
setDismissedSurvey(true)
@@ -24,14 +21,8 @@ export function SurveyWidgetDsNav() {
return null
}
// Hide the survey for users who have sidebar-navigation-ui-update:
// They've had it for months. We don't need their feedback anymore
if (hideDsSurvey && survey?.name === 'ds-nav') {
return null
}
return (
<div className={classnames('user-notifications', `survey-${survey.name}`)}>
<div className="user-notifications">
<div className="notification-entry">
<div role="alert" className="survey-notification">
<div className="notification-body">

View File

@@ -18,12 +18,6 @@ export default function SurveyWidget() {
return null
}
// Short-term hard-coded special case: hide the "DS nav" survey for users on
// the default variant
if (survey?.name === 'ds-nav') {
return null
}
return (
<div className="user-notifications">
<div className="notification-entry">

View File

@@ -1,16 +1,4 @@
import { createContext, type FC, type ReactNode, useContext } from 'react'
import { useSplitTestContext } from '@/shared/context/split-test-context'
import getMeta from '@/utils/meta'
export const hasDsNav = () => getMeta('ol-ExposedSettings').isOverleaf
/**
* This hook returns whether the user has the split-test assignment 'sidebar-navigation-ui-update'
*/
export const useHideDsSurvey = () => {
const { splitTestVariants } = useSplitTestContext()
return splitTestVariants['sidebar-navigation-ui-update'] === 'active'
}
/**
* This context wraps elements that should be styled according to the sidebar-navigation-ui-update redesign

View File

@@ -1490,7 +1490,6 @@
"organization_name": "Organization name",
"organization_or_company_name": "Organization or company name",
"organization_or_company_type": "Organization or company type",
"organize_projects": "Organize Projects",
"organize_tags": "Organize Tags",
"original_price": "Original price",
"other": "Other",