mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-28 19:41:33 +02:00
[web] Tear down Bootstrap 5 project dashboard feature flag (#21820)
* Remove `BootstrapVersionSwitcher` * Remove `bsVersion` * Remove `bootstrap-5-project-dashboard` from code * Fix frontend test * Fixup classname `"me-auto"` Co-authored-by: Tim Down <tim.down@overleaf.com> * Rename `handleClickBS5` to `handleClick` Co-authored-by: Tim Down <tim.down@overleaf.com> * Remove `bs3Props` from `project-list/components` * Remove translation: `joining` * Set `isBootstrap5()` to true in test, and update --------- Co-authored-by: Tim Down <tim.down@overleaf.com> GitOrigin-RevId: 94005bc8636c9235253758b8ac18a732ee1dbd77
This commit is contained in:
@@ -440,15 +440,6 @@ async function projectListPage(req, res, next) {
|
||||
)
|
||||
}
|
||||
|
||||
// Get the user's assignment for this page's Bootstrap 5 split test, which
|
||||
// populates splitTestVariants with a value for the split test name and allows
|
||||
// Pug to read it
|
||||
await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
res,
|
||||
'bootstrap-5-project-dashboard'
|
||||
)
|
||||
|
||||
res.render('project/list-react', {
|
||||
title: 'your_projects',
|
||||
usersBestSubscription,
|
||||
|
||||
@@ -6,7 +6,6 @@ block entrypointVar
|
||||
block vars
|
||||
- var suppressNavContentLinks = true
|
||||
- bootstrap5PageStatus = 'enabled' // One of 'disabled', 'enabled', and 'queryStringOnly'
|
||||
- bootstrap5PageSplitTest = 'bootstrap-5-project-dashboard'
|
||||
|
||||
block append meta
|
||||
meta(name="ol-usersBestSubscription" data-type="json" content=usersBestSubscription)
|
||||
|
||||
@@ -764,7 +764,6 @@
|
||||
"join_project": "",
|
||||
"join_team_explanation": "",
|
||||
"joined_team": "",
|
||||
"joining": "",
|
||||
"justify": "",
|
||||
"kb_suggestions_enquiry": "",
|
||||
"keep_current_plan": "",
|
||||
|
||||
@@ -2,9 +2,6 @@ import { useTranslation, Trans } from 'react-i18next'
|
||||
import { CommonsPlanSubscription } from '../../../../../../types/project/dashboard/subscription'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
|
||||
type CommonsPlanProps = Pick<
|
||||
CommonsPlanSubscription,
|
||||
@@ -23,14 +20,7 @@ function CommonsPlan({
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={classnames(
|
||||
'current-plan-label',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
>
|
||||
{currentPlanLabel}
|
||||
</span>
|
||||
<span className="current-plan-label d-md-none">{currentPlanLabel}</span>
|
||||
<OLTooltip
|
||||
description={t('commons_plan_tooltip', {
|
||||
plan: plan.name,
|
||||
@@ -41,21 +31,10 @@ function CommonsPlan({
|
||||
>
|
||||
<a
|
||||
href={featuresPageURL}
|
||||
className={classnames(
|
||||
'current-plan-label',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-inline-block',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
className="current-plan-label d-none d-md-inline-block"
|
||||
>
|
||||
{currentPlanLabel}
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<span className="info-badge" />}
|
||||
bs5={
|
||||
<MaterialIcon type="info" className="current-plan-label-icon" />
|
||||
}
|
||||
/>
|
||||
<MaterialIcon type="info" className="current-plan-label-icon" />
|
||||
</a>
|
||||
</OLTooltip>
|
||||
</>
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { FreePlanSubscription } from '../../../../../../types/project/dashboard/subscription'
|
||||
import * as eventTracking from '../../../../infrastructure/event-tracking'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
|
||||
type FreePlanProps = Pick<FreePlanSubscription, 'featuresPageURL'>
|
||||
|
||||
@@ -27,14 +24,7 @@ function FreePlan({ featuresPageURL }: FreePlanProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={classnames(
|
||||
'current-plan-label',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
>
|
||||
{currentPlanLabel}
|
||||
</span>
|
||||
<span className="current-plan-label d-md-none">{currentPlanLabel}</span>
|
||||
<OLTooltip
|
||||
description={t('free_plan_tooltip')}
|
||||
id="free-plan"
|
||||
@@ -42,26 +32,13 @@ function FreePlan({ featuresPageURL }: FreePlanProps) {
|
||||
>
|
||||
<a
|
||||
href={featuresPageURL}
|
||||
className={classnames(
|
||||
'current-plan-label',
|
||||
bsVersion({ bs5: 'd-none d-md-inline-block', bs3: 'hidden-xs' })
|
||||
)}
|
||||
className="current-plan-label d-none d-md-inline-block"
|
||||
>
|
||||
{currentPlanLabel}
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<span className="info-badge" />}
|
||||
bs5={
|
||||
<MaterialIcon type="info" className="current-plan-label-icon" />
|
||||
}
|
||||
/>
|
||||
<MaterialIcon type="info" className="current-plan-label-icon" />
|
||||
</a>
|
||||
</OLTooltip>{' '}
|
||||
<span
|
||||
className={bsVersion({
|
||||
bs5: 'd-none d-md-inline-block',
|
||||
bs3: 'hidden-xs',
|
||||
})}
|
||||
>
|
||||
<span className="d-none d-md-inline-block">
|
||||
<OLButton
|
||||
variant="primary"
|
||||
href="/user/subscription/plans"
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { GroupPlanSubscription } from '../../../../../../types/project/dashboard/subscription'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
|
||||
type GroupPlanProps = Pick<
|
||||
GroupPlanSubscription,
|
||||
@@ -37,14 +34,7 @@ function GroupPlan({
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={classnames(
|
||||
'current-plan-label',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
>
|
||||
{currentPlanLabel}
|
||||
</span>
|
||||
<span className="current-plan-label d-md-none">{currentPlanLabel}</span>
|
||||
<OLTooltip
|
||||
description={
|
||||
subscription.teamName != null
|
||||
@@ -59,21 +49,10 @@ function GroupPlan({
|
||||
>
|
||||
<a
|
||||
href={featuresPageURL}
|
||||
className={classnames(
|
||||
'current-plan-label',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-inline-block',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
className="current-plan-label d-none d-md-inline-block"
|
||||
>
|
||||
{currentPlanLabel}
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<span className="info-badge" />}
|
||||
bs5={
|
||||
<MaterialIcon type="info" className="current-plan-label-icon" />
|
||||
}
|
||||
/>
|
||||
<MaterialIcon type="info" className="current-plan-label-icon" />
|
||||
</a>
|
||||
</OLTooltip>
|
||||
</>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { IndividualPlanSubscription } from '../../../../../../types/project/dashboard/subscription'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
|
||||
type IndividualPlanProps = Pick<
|
||||
IndividualPlanSubscription,
|
||||
@@ -36,14 +33,7 @@ function IndividualPlan({
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={classnames(
|
||||
'current-plan-label',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
>
|
||||
{currentPlanLabel}
|
||||
</span>
|
||||
<span className="current-plan-label d-md-none">{currentPlanLabel}</span>
|
||||
<OLTooltip
|
||||
description={t('plan_tooltip', { plan: plan.name })}
|
||||
id="individual-plan"
|
||||
@@ -51,18 +41,10 @@ function IndividualPlan({
|
||||
>
|
||||
<a
|
||||
href={featuresPageURL}
|
||||
className={classnames(
|
||||
'current-plan-label',
|
||||
bsVersion({ bs5: 'd-none d-md-inline-block', bs3: 'hidden-xs' })
|
||||
)}
|
||||
className="current-plan-label d-none d-md-inline-block"
|
||||
>
|
||||
{currentPlanLabel}
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<span className="info-badge" />}
|
||||
bs5={
|
||||
<MaterialIcon type="info" className="current-plan-label-icon" />
|
||||
}
|
||||
/>
|
||||
<MaterialIcon type="info" className="current-plan-label-icon" />
|
||||
</a>
|
||||
</OLTooltip>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { useState, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dropdown as BS3Dropdown } from 'react-bootstrap'
|
||||
import { Spinner } from 'react-bootstrap-5'
|
||||
import {
|
||||
Dropdown,
|
||||
@@ -8,8 +6,6 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import MenuItemButton from './menu-item-button'
|
||||
import Icon from '../../../../shared/components/icon'
|
||||
import CopyProjectButton from '../table/cells/action-buttons/copy-project-button'
|
||||
import DownloadProjectButton from '../table/cells/action-buttons/download-project-button'
|
||||
import ArchiveProjectButton from '../table/cells/action-buttons/archive-project-button'
|
||||
@@ -22,256 +18,12 @@ import { Project } from '../../../../../../types/project/dashboard/api'
|
||||
import CompileAndDownloadProjectPDFButton from '../table/cells/action-buttons/compile-and-download-project-pdf-button'
|
||||
import RenameProjectButton from '../table/cells/action-buttons/rename-project-button'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
type ActionButtonProps = {
|
||||
project: Project
|
||||
onClick: <T extends React.MouseEvent>(e?: T, fn?: (e?: T) => void) => void
|
||||
}
|
||||
|
||||
function CopyProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
|
||||
return (
|
||||
<CopyProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<MenuItemButton
|
||||
onClick={e => handleOpenModal(e, onClick)}
|
||||
className="projects-action-menu-item"
|
||||
>
|
||||
<Icon type="files-o" className="menu-item-button-icon" />{' '}
|
||||
<span className="menu-item-button-text">{text}</span>
|
||||
</MenuItemButton>
|
||||
)}
|
||||
</CopyProjectButton>
|
||||
)
|
||||
}
|
||||
|
||||
function CompileAndDownloadProjectPDFButtonMenuItem({
|
||||
project,
|
||||
onClick,
|
||||
}: ActionButtonProps) {
|
||||
return (
|
||||
<CompileAndDownloadProjectPDFButton project={project}>
|
||||
{(text, pendingCompile, downloadProject) => (
|
||||
<MenuItemButton
|
||||
onClick={e => downloadProject(e, onClick)}
|
||||
className="projects-action-menu-item"
|
||||
>
|
||||
{pendingCompile ? (
|
||||
<Icon type="spinner" spin className="menu-item-button-icon" />
|
||||
) : (
|
||||
<Icon type="file-pdf-o" className="menu-item-button-icon" />
|
||||
)}{' '}
|
||||
<span className="menu-item-button-text">{text}</span>
|
||||
</MenuItemButton>
|
||||
)}
|
||||
</CompileAndDownloadProjectPDFButton>
|
||||
)
|
||||
}
|
||||
|
||||
function DownloadProjectButtonMenuItem({
|
||||
project,
|
||||
onClick,
|
||||
}: ActionButtonProps) {
|
||||
const handleClick = (downloadProject: () => void) => {
|
||||
downloadProject()
|
||||
onClick()
|
||||
}
|
||||
|
||||
return (
|
||||
<DownloadProjectButton project={project}>
|
||||
{(text, downloadProject) => (
|
||||
<MenuItemButton
|
||||
onClick={() => handleClick(downloadProject)}
|
||||
className="projects-action-menu-item"
|
||||
>
|
||||
<Icon type="cloud-download" className="menu-item-button-icon" />{' '}
|
||||
<span className="menu-item-button-text">{text}</span>
|
||||
</MenuItemButton>
|
||||
)}
|
||||
</DownloadProjectButton>
|
||||
)
|
||||
}
|
||||
|
||||
function ArchiveProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
|
||||
const handleClick = (handleOpenModal: () => void) => {
|
||||
handleOpenModal()
|
||||
onClick()
|
||||
}
|
||||
|
||||
return (
|
||||
<ArchiveProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<MenuItemButton
|
||||
onClick={() => handleClick(handleOpenModal)}
|
||||
className="projects-action-menu-item"
|
||||
>
|
||||
<Icon type="inbox" className="menu-item-button-icon" />{' '}
|
||||
<span className="menu-item-button-text">{text}</span>
|
||||
</MenuItemButton>
|
||||
)}
|
||||
</ArchiveProjectButton>
|
||||
)
|
||||
}
|
||||
|
||||
function TrashProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
|
||||
const handleClick = (handleOpenModal: () => void) => {
|
||||
handleOpenModal()
|
||||
onClick()
|
||||
}
|
||||
|
||||
return (
|
||||
<TrashProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<MenuItemButton
|
||||
onClick={() => handleClick(handleOpenModal)}
|
||||
className="projects-action-menu-item"
|
||||
>
|
||||
<Icon type="trash" className="menu-item-button-icon" />{' '}
|
||||
<span className="menu-item-button-text">{text}</span>
|
||||
</MenuItemButton>
|
||||
)}
|
||||
</TrashProjectButton>
|
||||
)
|
||||
}
|
||||
|
||||
function UnarchiveProjectButtonMenuItem({
|
||||
project,
|
||||
onClick,
|
||||
}: ActionButtonProps) {
|
||||
const handleClick = (unarchiveProject: () => Promise<void>) => {
|
||||
unarchiveProject()
|
||||
onClick()
|
||||
}
|
||||
|
||||
return (
|
||||
<UnarchiveProjectButton project={project}>
|
||||
{(text, unarchiveProject) => (
|
||||
<MenuItemButton
|
||||
onClick={() => handleClick(unarchiveProject)}
|
||||
className="projects-action-menu-item"
|
||||
>
|
||||
<Icon type="reply" className="menu-item-button-icon" />{' '}
|
||||
<span className="menu-item-button-text">{text}</span>
|
||||
</MenuItemButton>
|
||||
)}
|
||||
</UnarchiveProjectButton>
|
||||
)
|
||||
}
|
||||
|
||||
function UntrashProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
|
||||
const handleClick = (untrashProject: () => Promise<void>) => {
|
||||
untrashProject()
|
||||
onClick()
|
||||
}
|
||||
|
||||
return (
|
||||
<UntrashProjectButton project={project}>
|
||||
{(text, untrashProject) => (
|
||||
<MenuItemButton
|
||||
onClick={() => handleClick(untrashProject)}
|
||||
className="projects-action-menu-item"
|
||||
>
|
||||
<Icon type="reply" className="menu-item-button-icon" />{' '}
|
||||
<span className="menu-item-button-text">{text}</span>
|
||||
</MenuItemButton>
|
||||
)}
|
||||
</UntrashProjectButton>
|
||||
)
|
||||
}
|
||||
|
||||
function LeaveProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
|
||||
return (
|
||||
<LeaveProjectButton project={project}>
|
||||
{text => (
|
||||
<MenuItemButton onClick={onClick} className="projects-action-menu-item">
|
||||
<Icon type="sign-out" className="menu-item-button-icon" />{' '}
|
||||
<span className="menu-item-button-text">{text}</span>
|
||||
</MenuItemButton>
|
||||
)}
|
||||
</LeaveProjectButton>
|
||||
)
|
||||
}
|
||||
|
||||
function DeleteProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
|
||||
return (
|
||||
<DeleteProjectButton project={project}>
|
||||
{text => (
|
||||
<MenuItemButton onClick={onClick} className="projects-action-menu-item">
|
||||
<Icon type="ban" className="menu-item-button-icon" />{' '}
|
||||
<span className="menu-item-button-text">{text}</span>
|
||||
</MenuItemButton>
|
||||
)}
|
||||
</DeleteProjectButton>
|
||||
)
|
||||
}
|
||||
|
||||
function RenameProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
|
||||
const handleClick = (handleOpenModal: () => void) => {
|
||||
handleOpenModal()
|
||||
onClick()
|
||||
}
|
||||
return (
|
||||
<RenameProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<MenuItemButton
|
||||
onClick={() => handleClick(handleOpenModal)}
|
||||
className="projects-action-menu-item"
|
||||
>
|
||||
<Icon type="pencil" className="menu-item-button-icon" />{' '}
|
||||
<span className="menu-item-button-text">{text}</span>
|
||||
</MenuItemButton>
|
||||
)}
|
||||
</RenameProjectButton>
|
||||
)
|
||||
}
|
||||
|
||||
type ActionDropdownProps = {
|
||||
project: Project
|
||||
}
|
||||
|
||||
export function BS3ActionsDropdown({ project }: ActionDropdownProps) {
|
||||
const [isOpened, setIsOpened] = useState(false)
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setIsOpened(false)
|
||||
}, [setIsOpened])
|
||||
|
||||
return (
|
||||
<BS3Dropdown
|
||||
id={`project-actions-dropdown-${project.id}`}
|
||||
pullRight
|
||||
open={isOpened}
|
||||
onToggle={open => setIsOpened(open)}
|
||||
>
|
||||
<BS3Dropdown.Toggle noCaret className="btn-transparent">
|
||||
<Icon type="ellipsis-h" fw />
|
||||
</BS3Dropdown.Toggle>
|
||||
<BS3Dropdown.Menu className="projects-dropdown-menu text-left">
|
||||
<RenameProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
<CopyProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
<DownloadProjectButtonMenuItem
|
||||
project={project}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
<CompileAndDownloadProjectPDFButtonMenuItem
|
||||
project={project}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
<ArchiveProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
<TrashProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
<UnarchiveProjectButtonMenuItem
|
||||
project={project}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
<UntrashProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
<LeaveProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
<DeleteProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
</BS3Dropdown.Menu>
|
||||
</BS3Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
function BS5ActionsDropdown({ project }: ActionDropdownProps) {
|
||||
function ActionsDropdown({ project }: ActionDropdownProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
@@ -434,13 +186,4 @@ function BS5ActionsDropdown({ project }: ActionDropdownProps) {
|
||||
)
|
||||
}
|
||||
|
||||
function ActionsDropdown({ project }: ActionDropdownProps) {
|
||||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<BS3ActionsDropdown project={project} />}
|
||||
bs5={<BS5ActionsDropdown project={project} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ActionsDropdown
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
Dropdown as BS3Dropdown,
|
||||
MenuItem as BS3MenuItem,
|
||||
} from 'react-bootstrap'
|
||||
import Icon from '../../../../shared/components/icon'
|
||||
import {
|
||||
Filter,
|
||||
UNCATEGORIZED_KEY,
|
||||
@@ -19,8 +14,6 @@ import {
|
||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import ProjectsFilterMenu from '../projects-filter-menu'
|
||||
import TagsList from '../tags-list'
|
||||
import MenuItemButton from './menu-item-button'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
type ItemProps = {
|
||||
filter: Filter
|
||||
@@ -38,30 +31,15 @@ export function Item({ filter, text, onClick }: ItemProps) {
|
||||
return (
|
||||
<ProjectsFilterMenu filter={filter}>
|
||||
{isActive => (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<MenuItemButton
|
||||
onClick={handleClick}
|
||||
className="projects-types-menu-item"
|
||||
>
|
||||
{isActive ? (
|
||||
<Icon type="check" className="menu-item-button-icon" />
|
||||
) : null}
|
||||
<span className="menu-item-button-text">{text}</span>
|
||||
</MenuItemButton>
|
||||
}
|
||||
bs5={
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={handleClick}
|
||||
trailingIcon={isActive ? 'check' : undefined}
|
||||
active={isActive}
|
||||
>
|
||||
{text}
|
||||
</DropdownItem>
|
||||
}
|
||||
/>
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={handleClick}
|
||||
trailingIcon={isActive ? 'check' : undefined}
|
||||
active={isActive}
|
||||
>
|
||||
{text}
|
||||
</DropdownItem>
|
||||
)}
|
||||
</ProjectsFilterMenu>
|
||||
)
|
||||
@@ -70,7 +48,6 @@ export function Item({ filter, text, onClick }: ItemProps) {
|
||||
function ProjectsDropdown() {
|
||||
const { t } = useTranslation()
|
||||
const [title, setTitle] = useState(() => t('all_projects'))
|
||||
const [isOpened, setIsOpened] = useState(false)
|
||||
const { filter, selectedTagId, tags } = useProjectListContext()
|
||||
const filterTranslations = useRef<Record<Filter, string>>({
|
||||
all: t('all_projects'),
|
||||
@@ -79,7 +56,6 @@ function ProjectsDropdown() {
|
||||
archived: t('archived_projects'),
|
||||
trashed: t('trashed_projects'),
|
||||
})
|
||||
const handleClose = () => setIsOpened(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTagId === undefined) {
|
||||
@@ -98,84 +74,37 @@ function ProjectsDropdown() {
|
||||
}, [filter, tags, selectedTagId, t])
|
||||
|
||||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<BS3Dropdown
|
||||
id="projects-types-dropdown"
|
||||
open={isOpened}
|
||||
onToggle={open => setIsOpened(open)}
|
||||
>
|
||||
<BS3Dropdown.Toggle
|
||||
bsSize="large"
|
||||
noCaret
|
||||
className="ps-0 btn-transparent"
|
||||
>
|
||||
<span className="text-truncate me-1">{title}</span>
|
||||
<Icon type="angle-down" />
|
||||
</BS3Dropdown.Toggle>
|
||||
<BS3Dropdown.Menu className="projects-dropdown-menu">
|
||||
<Item filter="all" text={t('all_projects')} onClick={handleClose} />
|
||||
<Item
|
||||
filter="owned"
|
||||
text={t('your_projects')}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
<Item
|
||||
filter="shared"
|
||||
text={t('shared_with_you')}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
<Item
|
||||
filter="archived"
|
||||
text={t('archived_projects')}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
<Item
|
||||
filter="trashed"
|
||||
text={t('trashed_projects')}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
<BS3MenuItem header>{t('tags')}:</BS3MenuItem>
|
||||
<TagsList onTagClick={handleClose} onEditClick={handleClose} />
|
||||
</BS3Dropdown.Menu>
|
||||
</BS3Dropdown>
|
||||
}
|
||||
bs5={
|
||||
<Dropdown>
|
||||
<DropdownToggle
|
||||
id="projects-types-dropdown-toggle-btn"
|
||||
className="ps-0 mb-0 btn-transparent h3"
|
||||
size="lg"
|
||||
aria-label={t('filter_projects')}
|
||||
>
|
||||
<span className="text-truncate" aria-hidden>
|
||||
{title}
|
||||
</span>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu flip={false}>
|
||||
<li role="none">
|
||||
<Item filter="all" text={t('all_projects')} />
|
||||
</li>
|
||||
<li role="none">
|
||||
<Item filter="owned" text={t('your_projects')} />
|
||||
</li>
|
||||
<li role="none">
|
||||
<Item filter="shared" text={t('shared_with_you')} />
|
||||
</li>
|
||||
<li role="none">
|
||||
<Item filter="archived" text={t('archived_projects')} />
|
||||
</li>
|
||||
<li role="none">
|
||||
<Item filter="trashed" text={t('trashed_projects')} />
|
||||
</li>
|
||||
<DropdownHeader className="text-uppercase">
|
||||
{t('tags')}:
|
||||
</DropdownHeader>
|
||||
<TagsList />
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
<Dropdown>
|
||||
<DropdownToggle
|
||||
id="projects-types-dropdown-toggle-btn"
|
||||
className="ps-0 mb-0 btn-transparent h3"
|
||||
size="lg"
|
||||
aria-label={t('filter_projects')}
|
||||
>
|
||||
<span className="text-truncate" aria-hidden>
|
||||
{title}
|
||||
</span>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu flip={false}>
|
||||
<li role="none">
|
||||
<Item filter="all" text={t('all_projects')} />
|
||||
</li>
|
||||
<li role="none">
|
||||
<Item filter="owned" text={t('your_projects')} />
|
||||
</li>
|
||||
<li role="none">
|
||||
<Item filter="shared" text={t('shared_with_you')} />
|
||||
</li>
|
||||
<li role="none">
|
||||
<Item filter="archived" text={t('archived_projects')} />
|
||||
</li>
|
||||
<li role="none">
|
||||
<Item filter="trashed" text={t('trashed_projects')} />
|
||||
</li>
|
||||
<DropdownHeader className="text-uppercase">{t('tags')}:</DropdownHeader>
|
||||
<TagsList />
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
Dropdown as BS3Dropdown,
|
||||
MenuItem as BS3MenuItem,
|
||||
} from 'react-bootstrap'
|
||||
import Icon from '../../../../shared/components/icon'
|
||||
import useSort from '../../hooks/use-sort'
|
||||
import withContent, { SortBtnProps } from '../sort/with-content'
|
||||
import { useProjectListContext } from '../../context/project-list-context'
|
||||
import { Sort } from '../../../../../../types/project/dashboard/api'
|
||||
import MenuItemButton from './menu-item-button'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownHeader,
|
||||
@@ -17,31 +11,17 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
function Item({ onClick, text, iconType, screenReaderText }: SortBtnProps) {
|
||||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<MenuItemButton onClick={onClick} className="projects-sort-menu-item">
|
||||
{iconType ? (
|
||||
<Icon type={iconType} className="menu-item-button-icon" />
|
||||
) : null}
|
||||
<span className="menu-item-button-text">{text}</span>
|
||||
<span className="sr-only">{screenReaderText}</span>
|
||||
</MenuItemButton>
|
||||
}
|
||||
bs5={
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={onClick}
|
||||
trailingIcon={iconType}
|
||||
>
|
||||
{text}
|
||||
</DropdownItem>
|
||||
}
|
||||
/>
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={onClick}
|
||||
trailingIcon={iconType}
|
||||
>
|
||||
{text}
|
||||
</DropdownItem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -50,7 +30,6 @@ const ItemWithContent = withContent(Item)
|
||||
function SortByDropdown() {
|
||||
const { t } = useTranslation()
|
||||
const [title, setTitle] = useState(() => t('last_modified'))
|
||||
const [isOpened, setIsOpened] = useState(false)
|
||||
const { sort } = useProjectListContext()
|
||||
const { handleSort } = useSort()
|
||||
const sortByTranslations = useRef<Record<Sort['by'], string>>({
|
||||
@@ -60,11 +39,6 @@ function SortByDropdown() {
|
||||
})
|
||||
|
||||
const handleClick = (by: Sort['by']) => {
|
||||
setTitle(sortByTranslations.current[by])
|
||||
setIsOpened(false)
|
||||
handleSort(by)
|
||||
}
|
||||
const handleClickBS5 = (by: Sort['by']) => {
|
||||
setTitle(sortByTranslations.current[by])
|
||||
handleSort(by)
|
||||
}
|
||||
@@ -74,84 +48,41 @@ function SortByDropdown() {
|
||||
}, [sort.by])
|
||||
|
||||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<BS3Dropdown
|
||||
id="projects-sort-dropdown"
|
||||
className="projects-sort-dropdown"
|
||||
pullRight
|
||||
open={isOpened}
|
||||
onToggle={open => setIsOpened(open)}
|
||||
>
|
||||
<BS3Dropdown.Toggle
|
||||
bsSize="small"
|
||||
noCaret
|
||||
className="pe-0 btn-transparent"
|
||||
>
|
||||
<span className="text-truncate me-1">{title}</span>
|
||||
<Icon type="angle-down" />
|
||||
</BS3Dropdown.Toggle>
|
||||
<BS3Dropdown.Menu className="projects-dropdown-menu">
|
||||
<BS3MenuItem header>{t('sort_by')}:</BS3MenuItem>
|
||||
<ItemWithContent
|
||||
column="title"
|
||||
text={t('title')}
|
||||
sort={sort}
|
||||
onClick={() => handleClick('title')}
|
||||
/>
|
||||
<ItemWithContent
|
||||
column="owner"
|
||||
text={t('owner')}
|
||||
sort={sort}
|
||||
onClick={() => handleClick('owner')}
|
||||
/>
|
||||
<ItemWithContent
|
||||
column="lastUpdated"
|
||||
text={t('last_modified')}
|
||||
sort={sort}
|
||||
onClick={() => handleClick('lastUpdated')}
|
||||
/>
|
||||
</BS3Dropdown.Menu>
|
||||
</BS3Dropdown>
|
||||
}
|
||||
bs5={
|
||||
<Dropdown className="projects-sort-dropdown" align="end">
|
||||
<DropdownToggle
|
||||
id="projects-sort-dropdown"
|
||||
className="pe-0 mb-0 btn-transparent"
|
||||
size="sm"
|
||||
aria-label={t('sort_projects')}
|
||||
>
|
||||
<span className="text-truncate" aria-hidden>
|
||||
{title}
|
||||
</span>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu flip={false}>
|
||||
<DropdownHeader className="text-uppercase">
|
||||
{t('sort_by')}:
|
||||
</DropdownHeader>
|
||||
<ItemWithContent
|
||||
column="title"
|
||||
text={t('title')}
|
||||
sort={sort}
|
||||
onClick={() => handleClickBS5('title')}
|
||||
/>
|
||||
<ItemWithContent
|
||||
column="owner"
|
||||
text={t('owner')}
|
||||
sort={sort}
|
||||
onClick={() => handleClickBS5('owner')}
|
||||
/>
|
||||
<ItemWithContent
|
||||
column="lastUpdated"
|
||||
text={t('last_modified')}
|
||||
sort={sort}
|
||||
onClick={() => handleClickBS5('lastUpdated')}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
<Dropdown className="projects-sort-dropdown" align="end">
|
||||
<DropdownToggle
|
||||
id="projects-sort-dropdown"
|
||||
className="pe-0 mb-0 btn-transparent"
|
||||
size="sm"
|
||||
aria-label={t('sort_projects')}
|
||||
>
|
||||
<span className="text-truncate" aria-hidden>
|
||||
{title}
|
||||
</span>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu flip={false}>
|
||||
<DropdownHeader className="text-uppercase">
|
||||
{t('sort_by')}:
|
||||
</DropdownHeader>
|
||||
<ItemWithContent
|
||||
column="title"
|
||||
text={t('title')}
|
||||
sort={sort}
|
||||
onClick={() => handleClick('title')}
|
||||
/>
|
||||
<ItemWithContent
|
||||
column="owner"
|
||||
text={t('owner')}
|
||||
sort={sort}
|
||||
onClick={() => handleClick('owner')}
|
||||
/>
|
||||
<ItemWithContent
|
||||
column="lastUpdated"
|
||||
text={t('last_modified')}
|
||||
sort={sort}
|
||||
onClick={() => handleClick('lastUpdated')}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -129,9 +129,6 @@ export default function CreateTagModal({
|
||||
status === 'pending' || !tagName?.length || !!validationError
|
||||
}
|
||||
isLoading={isLoading}
|
||||
bs3Props={{
|
||||
loading: isLoading ? `${t('creating')}…` : t('create'),
|
||||
}}
|
||||
>
|
||||
{t('create')}
|
||||
</OLButton>
|
||||
|
||||
@@ -72,9 +72,6 @@ export default function DeleteTagModal({
|
||||
variant="danger"
|
||||
disabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
bs3Props={{
|
||||
loading: isLoading ? `${t('deleting')}…` : t('delete'),
|
||||
}}
|
||||
>
|
||||
{t('delete')}
|
||||
</OLButton>
|
||||
|
||||
@@ -138,9 +138,6 @@ export function EditTagModal({ id, tag, onEdit, onClose }: EditTagModalProps) {
|
||||
!!validationError
|
||||
}
|
||||
isLoading={isLoading}
|
||||
bs3Props={{
|
||||
loading: isLoading ? `${t('saving')}…` : t('save'),
|
||||
}}
|
||||
>
|
||||
{t('save')}
|
||||
</OLButton>
|
||||
|
||||
@@ -18,9 +18,7 @@ import OLForm from '@/features/ui/components/ol/ol-form'
|
||||
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import Notification from '@/shared/components/notification'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type ManageTagModalProps = {
|
||||
id: string
|
||||
@@ -125,12 +123,9 @@ export function ManageTagModal({
|
||||
<OLButton
|
||||
variant="danger"
|
||||
onClick={() => runDeleteTag(tag._id)}
|
||||
className={bsVersion({ bs3: 'pull-left', bs5: 'me-auto' })}
|
||||
className="me-auto"
|
||||
disabled={isDeleteLoading || isUpdateLoading}
|
||||
isLoading={isDeleteLoading}
|
||||
bs3Props={{
|
||||
loading: isDeleteLoading ? `${t('deleting')}…` : t('delete_tag'),
|
||||
}}
|
||||
>
|
||||
{t('delete_tag')}
|
||||
</OLButton>
|
||||
@@ -151,15 +146,9 @@ export function ManageTagModal({
|
||||
(newTagName === tag?.name && selectedColor === getTagColor(tag))
|
||||
)}
|
||||
isLoading={isUpdateLoading}
|
||||
bs3Props={{
|
||||
loading: isUpdateLoading
|
||||
? `${t('saving')}…`
|
||||
: t('save_or_cancel-save'),
|
||||
}}
|
||||
>
|
||||
{t('save_or_cancel-save')}
|
||||
</OLButton>
|
||||
<BootstrapVersionSwitcher bs3={<div className="clearfix" />} />
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { type JSXElementConstructor, useCallback, useState } from 'react'
|
||||
import {
|
||||
Dropdown as BS3Dropdown,
|
||||
MenuItem as BS3MenuItem,
|
||||
} from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ControlledDropdown from '../../../shared/components/controlled-dropdown'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import NewProjectButtonModal, {
|
||||
NewProjectButtonModalVariant,
|
||||
@@ -13,7 +8,6 @@ import AddAffiliation, { useAddAffiliation } from './add-affiliation'
|
||||
import { Nullable } from '../../../../../types/utils'
|
||||
import { sendMB } from '../../../infrastructure/event-tracking'
|
||||
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownDivider,
|
||||
@@ -151,225 +145,119 @@ function NewProjectButton({
|
||||
|
||||
return (
|
||||
<>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<ControlledDropdown id={id} className={className}>
|
||||
<BS3Dropdown.Toggle
|
||||
noCaret
|
||||
className="new-project-button"
|
||||
bsStyle="primary"
|
||||
<Dropdown className={className} onSelect={handleMainButtonClick}>
|
||||
<DropdownToggle
|
||||
id={id}
|
||||
className="new-project-button"
|
||||
variant="primary"
|
||||
>
|
||||
{buttonText || t('new_project')}
|
||||
</DropdownToggle>
|
||||
<DropdownMenu>
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
onClick={e =>
|
||||
handleModalMenuClick(e, {
|
||||
modalVariant: 'blank_project',
|
||||
dropdownMenuEvent: 'blank-project',
|
||||
})
|
||||
}
|
||||
>
|
||||
{buttonText || t('new_project')}
|
||||
</BS3Dropdown.Toggle>
|
||||
<BS3Dropdown.Menu>
|
||||
<BS3MenuItem
|
||||
onClick={(e: React.MouseEvent) =>
|
||||
handleModalMenuClick(e, {
|
||||
modalVariant: 'blank_project',
|
||||
dropdownMenuEvent: 'blank-project',
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('blank_project')}
|
||||
</BS3MenuItem>
|
||||
<BS3MenuItem
|
||||
onClick={(e: React.MouseEvent) =>
|
||||
handleModalMenuClick(e, {
|
||||
modalVariant: 'example_project',
|
||||
dropdownMenuEvent: 'example-project',
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('example_project')}
|
||||
</BS3MenuItem>
|
||||
<BS3MenuItem
|
||||
onClick={(e: React.MouseEvent) =>
|
||||
handleModalMenuClick(e, {
|
||||
modalVariant: 'upload_project',
|
||||
dropdownMenuEvent: 'upload-project',
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('upload_project')}
|
||||
</BS3MenuItem>
|
||||
{ImportProjectFromGithubMenu && (
|
||||
<ImportProjectFromGithubMenu
|
||||
onClick={e =>
|
||||
handleModalMenuClick(e, {
|
||||
modalVariant: 'import_from_github',
|
||||
dropdownMenuEvent: 'import-from-github',
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{portalTemplates.length > 0 ? (
|
||||
<>
|
||||
<BS3MenuItem divider />
|
||||
<div aria-hidden="true" className="dropdown-header">
|
||||
{`${t('institution')} ${t('templates')}`}
|
||||
</div>
|
||||
{portalTemplates.map((portalTemplate, index) => (
|
||||
<BS3MenuItem
|
||||
key={`portal-template-${index}`}
|
||||
href={`${portalTemplate.url}#templates`}
|
||||
onClick={(e: React.MouseEvent) =>
|
||||
handlePortalTemplateClick(e, portalTemplate.name)
|
||||
}
|
||||
>
|
||||
{portalTemplate.name}
|
||||
</BS3MenuItem>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{templateLinks && templateLinks.length > 0 && (
|
||||
<>
|
||||
<BS3MenuItem divider />
|
||||
<div aria-hidden="true" className="dropdown-header">
|
||||
{t('templates')}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{templateLinks?.map((templateLink, index) => (
|
||||
<BS3MenuItem
|
||||
key={`new-project-button-template-${index}`}
|
||||
href={templateLink.url}
|
||||
onClick={(e: React.MouseEvent) =>
|
||||
handleStaticTemplateClick(e, templateLink.trackingKey)
|
||||
}
|
||||
>
|
||||
{templateLink.name === 'view_all'
|
||||
? t('view_all')
|
||||
: templateLink.name}
|
||||
</BS3MenuItem>
|
||||
))}
|
||||
{showAddAffiliationWidget && enableAddAffiliationWidget ? (
|
||||
<>
|
||||
<BS3MenuItem divider />
|
||||
<li className="add-affiliation-mobile-wrapper">
|
||||
<AddAffiliation className="is-mobile" />
|
||||
</li>
|
||||
</>
|
||||
) : null}
|
||||
</BS3Dropdown.Menu>
|
||||
</ControlledDropdown>
|
||||
}
|
||||
bs5={
|
||||
<Dropdown className={className} onSelect={handleMainButtonClick}>
|
||||
<DropdownToggle
|
||||
id={id}
|
||||
className="new-project-button"
|
||||
variant="primary"
|
||||
{t('blank_project')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
onClick={e =>
|
||||
handleModalMenuClick(e, {
|
||||
modalVariant: 'example_project',
|
||||
dropdownMenuEvent: 'example-project',
|
||||
})
|
||||
}
|
||||
>
|
||||
{buttonText || t('new_project')}
|
||||
</DropdownToggle>
|
||||
<DropdownMenu>
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
onClick={e =>
|
||||
handleModalMenuClick(e, {
|
||||
modalVariant: 'blank_project',
|
||||
dropdownMenuEvent: 'blank-project',
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('blank_project')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
onClick={e =>
|
||||
handleModalMenuClick(e, {
|
||||
modalVariant: 'example_project',
|
||||
dropdownMenuEvent: 'example-project',
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('example_project')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
onClick={e =>
|
||||
handleModalMenuClick(e, {
|
||||
modalVariant: 'upload_project',
|
||||
dropdownMenuEvent: 'upload-project',
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('upload_project')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
{ImportProjectFromGithubMenu && (
|
||||
<ImportProjectFromGithubMenu
|
||||
onClick={e =>
|
||||
handleModalMenuClick(e, {
|
||||
modalVariant: 'import_from_github',
|
||||
dropdownMenuEvent: 'import-from-github',
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
{portalTemplates.length > 0 ? (
|
||||
<>
|
||||
<DropdownDivider />
|
||||
<DropdownHeader aria-hidden="true">
|
||||
{`${t('institution')} ${t('templates')}`}
|
||||
</DropdownHeader>
|
||||
{portalTemplates.map((portalTemplate, index) => (
|
||||
<li role="none" key={`portal-template-${index}`}>
|
||||
<DropdownItem
|
||||
key={`portal-template-${index}`}
|
||||
href={`${portalTemplate.url}#templates`}
|
||||
onClick={e =>
|
||||
handlePortalTemplateClick(e, portalTemplate.name)
|
||||
}
|
||||
aria-label={`${portalTemplate.name} ${t('template')}`}
|
||||
>
|
||||
{portalTemplate.name}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{templateLinks && templateLinks.length > 0 && (
|
||||
<>
|
||||
<DropdownDivider />
|
||||
<DropdownHeader aria-hidden="true">
|
||||
{t('templates')}
|
||||
</DropdownHeader>
|
||||
</>
|
||||
)}
|
||||
{templateLinks?.map((templateLink, index) => (
|
||||
<li role="none" key={`new-project-button-template-${index}`}>
|
||||
{t('example_project')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
onClick={e =>
|
||||
handleModalMenuClick(e, {
|
||||
modalVariant: 'upload_project',
|
||||
dropdownMenuEvent: 'upload-project',
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('upload_project')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
{ImportProjectFromGithubMenu && (
|
||||
<ImportProjectFromGithubMenu
|
||||
onClick={e =>
|
||||
handleModalMenuClick(e, {
|
||||
modalVariant: 'import_from_github',
|
||||
dropdownMenuEvent: 'import-from-github',
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
{portalTemplates.length > 0 ? (
|
||||
<>
|
||||
<DropdownDivider />
|
||||
<DropdownHeader aria-hidden="true">
|
||||
{`${t('institution')} ${t('templates')}`}
|
||||
</DropdownHeader>
|
||||
{portalTemplates.map((portalTemplate, index) => (
|
||||
<li role="none" key={`portal-template-${index}`}>
|
||||
<DropdownItem
|
||||
href={templateLink.url}
|
||||
key={`portal-template-${index}`}
|
||||
href={`${portalTemplate.url}#templates`}
|
||||
onClick={e =>
|
||||
handleStaticTemplateClick(e, templateLink.trackingKey)
|
||||
handlePortalTemplateClick(e, portalTemplate.name)
|
||||
}
|
||||
aria-label={`${templateLink.name} ${t('template')}`}
|
||||
aria-label={`${portalTemplate.name} ${t('template')}`}
|
||||
>
|
||||
{templateLink.name === 'view_all'
|
||||
? t('view_all')
|
||||
: templateLink.name}
|
||||
{portalTemplate.name}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
))}
|
||||
{showAddAffiliationWidget && enableAddAffiliationWidget ? (
|
||||
<>
|
||||
<DropdownDivider />
|
||||
<li className="add-affiliation-mobile-wrapper">
|
||||
<AddAffiliation className="is-mobile" />
|
||||
</li>
|
||||
</>
|
||||
) : null}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{templateLinks && templateLinks.length > 0 && (
|
||||
<>
|
||||
<DropdownDivider />
|
||||
<DropdownHeader aria-hidden="true">
|
||||
{t('templates')}
|
||||
</DropdownHeader>
|
||||
</>
|
||||
)}
|
||||
{templateLinks?.map((templateLink, index) => (
|
||||
<li role="none" key={`new-project-button-template-${index}`}>
|
||||
<DropdownItem
|
||||
href={templateLink.url}
|
||||
onClick={e =>
|
||||
handleStaticTemplateClick(e, templateLink.trackingKey)
|
||||
}
|
||||
aria-label={`${templateLink.name} ${t('template')}`}
|
||||
>
|
||||
{templateLink.name === 'view_all'
|
||||
? t('view_all')
|
||||
: templateLink.name}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
))}
|
||||
{showAddAffiliationWidget && enableAddAffiliationWidget ? (
|
||||
<>
|
||||
<DropdownDivider />
|
||||
<li className="add-affiliation-mobile-wrapper">
|
||||
<AddAffiliation className="is-mobile" />
|
||||
</li>
|
||||
</>
|
||||
) : null}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<NewProjectButtonModal modal={modal} onHide={() => setModal(null)} />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -102,9 +102,6 @@ function ModalContentNewProjectForm({ onCancel, template = 'none' }: Props) {
|
||||
onClick={createNewProject}
|
||||
disabled={projectName === '' || isLoading}
|
||||
isLoading={isLoading}
|
||||
bs3Props={{
|
||||
loading: isLoading ? `${t('creating')}…` : t('create'),
|
||||
}}
|
||||
>
|
||||
{t('create')}
|
||||
</OLButton>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import Icon from '../../../../../../shared/components/icon'
|
||||
import getMeta from '../../../../../../utils/meta'
|
||||
import useAsync from '../../../../../../shared/hooks/use-async'
|
||||
import {
|
||||
@@ -88,13 +87,6 @@ function ReconfirmAffiliation({
|
||||
className="btn-inline-link"
|
||||
disabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
bs3Props={{
|
||||
loading: isLoading ? (
|
||||
<>
|
||||
<Icon type="refresh" spin fw /> {t('sending')}…
|
||||
</>
|
||||
) : null,
|
||||
}}
|
||||
>
|
||||
{t('resend_confirmation_email')}
|
||||
</OLButton>
|
||||
@@ -143,14 +135,6 @@ function ReconfirmAffiliation({
|
||||
action={
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
bs3Props={{
|
||||
loading:
|
||||
isLoading || isPending ? (
|
||||
<>
|
||||
<Icon type="refresh" spin fw /> {t('sending')}…
|
||||
</>
|
||||
) : null,
|
||||
}}
|
||||
isLoading={isLoading || isPending}
|
||||
disabled={isLoading || isPending}
|
||||
onClick={handleRequestReconfirmation}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import Notification from '../notification'
|
||||
import Icon from '../../../../../shared/components/icon'
|
||||
import getMeta from '../../../../../utils/meta'
|
||||
import useAsyncDismiss from '../hooks/useAsyncDismiss'
|
||||
import useAsync from '../../../../../shared/hooks/use-async'
|
||||
@@ -96,13 +95,6 @@ function CommonNotification({ notification }: CommonNotificationProps) {
|
||||
) : (
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
bs3Props={{
|
||||
loading: isLoading ? (
|
||||
<>
|
||||
<Icon type="spinner" spin /> {t('joining')}…
|
||||
</>
|
||||
) : null,
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
disabled={isLoading}
|
||||
onClick={() => handleAcceptInvite(notification)}
|
||||
|
||||
@@ -26,11 +26,8 @@ import withErrorBoundary from '../../../infrastructure/error-boundary'
|
||||
import { GenericErrorBoundaryFallback } from '../../../shared/components/generic-error-boundary-fallback'
|
||||
import { SplitTestProvider } from '@/shared/context/split-test-context'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
import Notification from '@/shared/components/notification'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { TableContainer } from '@/features/ui/components/bootstrap-5/table'
|
||||
|
||||
function ProjectListRoot() {
|
||||
@@ -78,13 +75,7 @@ function ProjectListPageContent() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const tableTopArea = (
|
||||
<div
|
||||
className={classnames(
|
||||
'pt-2',
|
||||
'pb-3',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
>
|
||||
<div className="pt-2 pb-3 d-md-none">
|
||||
<div className="clearfix">
|
||||
<NewProjectButton
|
||||
id="new-project-button-projects-table"
|
||||
@@ -110,12 +101,7 @@ function ProjectListPageContent() {
|
||||
<>
|
||||
<SystemMessages />
|
||||
|
||||
<div
|
||||
className={classnames(
|
||||
'project-list-wrapper',
|
||||
bsVersion({ bs3: 'clearfix container mx-0 px-0' })
|
||||
)}
|
||||
>
|
||||
<div className="project-list-wrapper">
|
||||
{totalProjectsCount > 0 ? (
|
||||
<>
|
||||
<Sidebar />
|
||||
@@ -131,43 +117,22 @@ function ProjectListPageContent() {
|
||||
filter={filter}
|
||||
selectedTag={selectedTag}
|
||||
selectedTagId={selectedTagId}
|
||||
className={classnames(
|
||||
'text-truncate',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-block',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
className="text-truncate d-none d-md-block"
|
||||
/>
|
||||
<div className="project-tools">
|
||||
<div
|
||||
className={bsVersion({
|
||||
bs5: 'd-none d-md-block',
|
||||
bs3: 'hidden-xs',
|
||||
})}
|
||||
>
|
||||
<div className="d-none d-md-block">
|
||||
{selectedProjects.length === 0 ? (
|
||||
<CurrentPlanWidget />
|
||||
) : (
|
||||
<ProjectTools />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={bsVersion({
|
||||
bs5: 'd-md-none',
|
||||
bs3: 'visible-xs',
|
||||
})}
|
||||
>
|
||||
<div className="d-md-none">
|
||||
<CurrentPlanWidget />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<OLRow
|
||||
className={bsVersion({
|
||||
bs5: 'd-none d-md-block',
|
||||
bs3: 'hidden-xs',
|
||||
})}
|
||||
>
|
||||
<OLRow className="d-none d-md-block">
|
||||
<OLCol lg={7}>
|
||||
<SearchForm
|
||||
inputValue={searchText}
|
||||
@@ -177,20 +142,10 @@ function ProjectListPageContent() {
|
||||
/>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
<div
|
||||
className={classnames(
|
||||
'project-list-sidebar-survey-wrapper',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
>
|
||||
<div className="project-list-sidebar-survey-wrapper d-md-none">
|
||||
<SurveyWidget />
|
||||
</div>
|
||||
<div
|
||||
className={classnames(
|
||||
'mt-1',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
>
|
||||
<div className="mt-1 d-md-none">
|
||||
<div
|
||||
role="toolbar"
|
||||
className="projects-toolbar"
|
||||
@@ -202,20 +157,10 @@ function ProjectListPageContent() {
|
||||
</div>
|
||||
<OLRow className="row-spaced">
|
||||
<OLCol>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<div className="card project-list-card">
|
||||
{tableTopArea}
|
||||
<ProjectListTable />
|
||||
</div>
|
||||
}
|
||||
bs5={
|
||||
<TableContainer bordered>
|
||||
{tableTopArea}
|
||||
<ProjectListTable />
|
||||
</TableContainer>
|
||||
}
|
||||
/>
|
||||
<TableContainer bordered>
|
||||
{tableTopArea}
|
||||
<ProjectListTable />
|
||||
</TableContainer>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
<OLRow className="row-spaced">
|
||||
@@ -253,11 +198,7 @@ function DashApiError() {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<OLRow className="row-spaced">
|
||||
<OLCol
|
||||
xs={{ span: 8, offset: 2 }}
|
||||
bs3Props={{ xs: 8, xsOffset: 2 }}
|
||||
aria-live="polite"
|
||||
>
|
||||
<OLCol xs={{ span: 8, offset: 2 }} aria-live="polite">
|
||||
<div className="notification-list">
|
||||
<Notification
|
||||
content={t('generic_something_went_wrong')}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FormControl } from 'react-bootstrap'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import classnames from 'classnames'
|
||||
import { Tag } from '../../../../../app/src/Features/Tags/types'
|
||||
@@ -12,8 +11,6 @@ import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type SearchFormOwnProps = {
|
||||
inputValue: string
|
||||
@@ -79,12 +76,9 @@ function SearchForm({
|
||||
className={classnames('project-search', className)}
|
||||
role="search"
|
||||
onSubmit={e => e.preventDefault()}
|
||||
bs3Props={{ horizontal: true }}
|
||||
{...props}
|
||||
>
|
||||
<OLFormGroup
|
||||
className={bsVersion({ bs3: 'has-feedback has-feedback-left' })}
|
||||
>
|
||||
<OLFormGroup>
|
||||
<OLCol>
|
||||
<OLFormControl
|
||||
type="text"
|
||||
@@ -92,25 +86,16 @@ function SearchForm({
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
aria-label={placeholder}
|
||||
prepend={BootstrapVersionSwitcher({
|
||||
bs3: <Icon type="search" />,
|
||||
bs5: <MaterialIcon type="search" />,
|
||||
})}
|
||||
prepend={<MaterialIcon type="search" />}
|
||||
append={
|
||||
inputValue.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className={bsVersion({
|
||||
bs3: 'project-search-clear-btn btn-link',
|
||||
bs5: 'project-search-clear-btn',
|
||||
})}
|
||||
className="project-search-clear-btn"
|
||||
aria-label={t('clear_search')}
|
||||
onClick={handleClear}
|
||||
>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="times" />}
|
||||
bs5={<MaterialIcon type="clear" />}
|
||||
/>
|
||||
<MaterialIcon type="clear" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ import SidebarFilters from './sidebar-filters'
|
||||
import AddAffiliation, { useAddAffiliation } from '../add-affiliation'
|
||||
import SurveyWidget from '../survey-widget'
|
||||
import { usePersistedResize } from '../../../../shared/hooks/use-resize'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
|
||||
function Sidebar() {
|
||||
const { show: showAddAffiliationWidget } = useAddAffiliation()
|
||||
@@ -14,10 +12,7 @@ function Sidebar() {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
'project-list-sidebar-wrapper-react',
|
||||
bsVersion({ bs5: 'd-none d-md-block', bs3: 'hidden-xs' })
|
||||
)}
|
||||
className="project-list-sidebar-wrapper-react d-none d-md-block"
|
||||
{...getTargetProps({
|
||||
style: {
|
||||
...(mousePos?.x && { flexBasis: `${mousePos.x}px` }),
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
export default function TagsList() {
|
||||
const { t } = useTranslation()
|
||||
@@ -76,65 +75,31 @@ export default function TagsList() {
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<BootstrapVersionSwitcher
|
||||
bs5={
|
||||
<Dropdown align="end" className="tag-menu">
|
||||
<DropdownToggle id={`${tag._id}-dropdown-toggle`}>
|
||||
<span className="caret" />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu className="dropdown-menu-sm-width">
|
||||
<DropdownItem
|
||||
as="li"
|
||||
className="tag-action"
|
||||
onClick={e => handleEditTag(e, tag._id)}
|
||||
>
|
||||
{t('edit')}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
as="li"
|
||||
className="tag-action"
|
||||
onClick={e => handleDeleteTag(e, tag._id)}
|
||||
>
|
||||
{t('delete')}
|
||||
</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
bs3={
|
||||
<span className="dropdown tag-menu">
|
||||
<button
|
||||
type="button"
|
||||
className="dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
dropdown-toggle=""
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span className="caret" />
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
onClick={e => handleEditTag(e, tag._id)}
|
||||
className="tag-action"
|
||||
>
|
||||
{t('edit')}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
onClick={e => handleDeleteTag(e, tag._id)}
|
||||
className="tag-action"
|
||||
>
|
||||
{t('delete')}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
||||
<Dropdown align="end" className="tag-menu">
|
||||
<DropdownToggle
|
||||
id={`${tag._id}-dropdown-toggle`}
|
||||
data-testid="tag-dropdown-toggle"
|
||||
>
|
||||
<span className="caret" />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu className="dropdown-menu-sm-width">
|
||||
<DropdownItem
|
||||
as="li"
|
||||
className="tag-action"
|
||||
onClick={e => handleEditTag(e, tag._id)}
|
||||
>
|
||||
{t('edit')}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
as="li"
|
||||
className="tag-action"
|
||||
onClick={e => handleDeleteTag(e, tag._id)}
|
||||
>
|
||||
{t('delete')}
|
||||
</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Sort } from '../../../../../../types/project/dashboard/api'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type SortBtnOwnProps = {
|
||||
column: string
|
||||
@@ -28,9 +27,7 @@ function withContent<T extends SortBtnOwnProps>(
|
||||
|
||||
if (column === sort.by) {
|
||||
iconType =
|
||||
sort.order === 'asc'
|
||||
? bsVersion({ bs5: 'arrow_upward_alt', bs3: 'caret-up' })
|
||||
: bsVersion({ bs5: 'arrow_downward_alt', bs3: 'caret-down' })
|
||||
sort.order === 'asc' ? 'arrow_upward_alt' : 'arrow_downward_alt'
|
||||
screenReaderText = t('reverse_x_sort_order', { x: text })
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import usePersistedState from '../../../shared/hooks/use-persisted-state'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import { useCallback } from 'react'
|
||||
import Close from '@/shared/components/close'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
export default function SurveyWidget() {
|
||||
const survey = getMeta('ol-survey')
|
||||
@@ -22,13 +21,7 @@ export default function SurveyWidget() {
|
||||
return (
|
||||
<div className="user-notifications">
|
||||
<div className="notification-entry">
|
||||
<div
|
||||
role="alert"
|
||||
className={bsVersion({
|
||||
bs3: 'alert alert-info-alt',
|
||||
bs5: 'survey-notification',
|
||||
})}
|
||||
>
|
||||
<div role="alert" className="survey-notification">
|
||||
<div className="notification-body">
|
||||
{survey.preText}
|
||||
<a
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import { archiveProject } from '../../../../util/api'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type ArchiveProjectButtonProps = {
|
||||
project: Project
|
||||
@@ -78,8 +77,7 @@ const ArchiveProjectButtonTooltip = memo(function ArchiveProjectButtonTooltip({
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={bsVersion({ bs5: 'inbox', bs3: 'inbox' })}
|
||||
bs3Props={{ fw: true }}
|
||||
icon="inbox"
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import Icon from '../../../../../../shared/components/icon'
|
||||
import * as eventTracking from '../../../../../../infrastructure/event-tracking'
|
||||
import { useLocation } from '../../../../../../shared/hooks/use-location'
|
||||
import useAbortController from '../../../../../../shared/hooks/use-abort-controller'
|
||||
@@ -15,7 +14,6 @@ import OLModal, {
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
|
||||
type CompileAndDownloadProjectPDFButtonProps = {
|
||||
@@ -159,13 +157,7 @@ const CompileAndDownloadProjectPDFButtonTooltip = memo(
|
||||
loadingLabel={text}
|
||||
isLoading={pendingCompile}
|
||||
className="action-btn"
|
||||
icon={bsVersion({ bs5: 'picture_as_pdf', bs3: 'file-pdf-o' })}
|
||||
bs3Props={{
|
||||
fw: true,
|
||||
loading: pendingCompile ? (
|
||||
<Icon type="spinner" fw accessibilityLabel={text} spin />
|
||||
) : null,
|
||||
}}
|
||||
icon="picture_as_pdf"
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
|
||||
@@ -12,7 +12,6 @@ import { useProjectTags } from '@/features/project-list/hooks/use-project-tags'
|
||||
import { isSmallDevice } from '../../../../../../infrastructure/event-tracking'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type CopyButtonProps = {
|
||||
project: Project
|
||||
@@ -111,8 +110,7 @@ const CopyProjectButtonTooltip = memo(function CopyProjectButtonTooltip({
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={bsVersion({ bs5: 'file_copy', bs3: 'files-o' })}
|
||||
bs3Props={{ fw: true }}
|
||||
icon="file_copy"
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
|
||||
@@ -8,7 +8,6 @@ import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import getMeta from '@/utils/meta'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type DeleteProjectButtonProps = {
|
||||
project: Project
|
||||
@@ -76,8 +75,7 @@ const DeleteProjectButtonTooltip = memo(function DeleteProjectButtonTooltip({
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={bsVersion({ bs5: 'block', bs3: 'ban' })}
|
||||
bs3Props={{ fw: true }}
|
||||
icon="block"
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useLocation } from '../../../../../../shared/hooks/use-location'
|
||||
import { isSmallDevice } from '../../../../../../infrastructure/event-tracking'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type DownloadProjectButtonProps = {
|
||||
project: Project
|
||||
@@ -52,8 +51,7 @@ const DownloadProjectButtonTooltip = memo(
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={bsVersion({ bs5: 'download', bs3: 'cloud-download' })}
|
||||
bs3Props={{ fw: true }}
|
||||
icon="download"
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
|
||||
@@ -8,7 +8,6 @@ import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import getMeta from '@/utils/meta'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type LeaveProjectButtonProps = {
|
||||
project: Project
|
||||
@@ -75,8 +74,7 @@ const LeaveProjectButtonTooltip = memo(function LeaveProjectButtonTooltip({
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={bsVersion({ bs5: 'logout', bs3: 'sign-out' })}
|
||||
bs3Props={{ fw: true }}
|
||||
icon="logout"
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import { trashProject } from '../../../../util/api'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type TrashProjectButtonProps = {
|
||||
project: Project
|
||||
@@ -75,8 +74,7 @@ const TrashProjectButtonTooltip = memo(function TrashProjectButtonTooltip({
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={bsVersion({ bs5: 'delete', bs3: 'trash' })}
|
||||
bs3Props={{ fw: true }}
|
||||
icon="delete"
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import { unarchiveProject } from '../../../../util/api'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type UnarchiveProjectButtonProps = {
|
||||
project: Project
|
||||
@@ -54,8 +53,7 @@ const UnarchiveProjectButtonTooltip = memo(
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={bsVersion({ bs5: 'restore_page', bs3: 'reply' })}
|
||||
bs3Props={{ fw: true }}
|
||||
icon="restore_page"
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import { untrashProject } from '../../../../util/api'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type UntrashProjectButtonProps = {
|
||||
project: Project
|
||||
@@ -53,8 +52,7 @@ const UntrashProjectButtonTooltip = memo(function UntrashProjectButtonTooltip({
|
||||
variant="link"
|
||||
accessibilityLabel={text}
|
||||
className="action-btn"
|
||||
icon={bsVersion({ bs5: 'restore_page', bs3: 'reply' })}
|
||||
bs3Props={{ fw: true }}
|
||||
icon="restore_page"
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { useCallback, useState, useRef } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Tag as TagType } from '../../../../../../../app/src/Features/Tags/types'
|
||||
import Icon from '../../../../../shared/components/icon'
|
||||
import { useProjectListContext } from '../../../context/project-list-context'
|
||||
import { removeProjectFromTag } from '../../../util/api'
|
||||
import classnames from 'classnames'
|
||||
import { getTagColor } from '../../../util/tag'
|
||||
import Tag from '@/features/ui/components/bootstrap-5/tag'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
type InlineTagsProps = {
|
||||
projectId: string
|
||||
@@ -36,17 +33,6 @@ type InlineTagProps = {
|
||||
function InlineTag({ tag, projectId }: InlineTagProps) {
|
||||
const { t } = useTranslation()
|
||||
const { selectTag, removeProjectFromTagInView } = useProjectListContext()
|
||||
const [classNames, setClassNames] = useState('')
|
||||
const tagLabelRef = useRef(null)
|
||||
const tagBtnRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const handleLabelClick = (e: React.MouseEvent) => {
|
||||
// trigger the click on the button only when the event
|
||||
// is triggered from the wrapper element
|
||||
if (e.target === tagLabelRef.current) {
|
||||
tagBtnRef.current?.click()
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemoveTag = useCallback(
|
||||
async (tagId: string, projectId: string) => {
|
||||
@@ -55,66 +41,25 @@ function InlineTag({ tag, projectId }: InlineTagProps) {
|
||||
},
|
||||
[removeProjectFromTagInView]
|
||||
)
|
||||
const handleCloseMouseOver = () => setClassNames('tag-label-close-hover')
|
||||
const handleCloseMouseOut = () => setClassNames('')
|
||||
|
||||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
className={classnames('tag-label', classNames)}
|
||||
onClick={handleLabelClick}
|
||||
ref={tagLabelRef}
|
||||
>
|
||||
<button
|
||||
className="label label-default tag-label-name"
|
||||
aria-label={t('select_tag', { tagName: tag.name })}
|
||||
ref={tagBtnRef}
|
||||
onClick={() => selectTag(tag._id)}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: getTagColor(tag),
|
||||
}}
|
||||
>
|
||||
<Icon type="circle" aria-hidden="true" />
|
||||
</span>{' '}
|
||||
{tag.name}
|
||||
</button>
|
||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
||||
<button
|
||||
className="label label-default tag-label-remove"
|
||||
aria-label={t('remove_tag', { tagName: tag.name })}
|
||||
onClick={() => handleRemoveTag(tag._id, projectId)}
|
||||
onMouseOver={handleCloseMouseOver}
|
||||
onMouseOut={handleCloseMouseOut}
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<Tag
|
||||
prepend={
|
||||
<i
|
||||
className="badge-tag-circle"
|
||||
style={{ backgroundColor: getTagColor(tag) }}
|
||||
/>
|
||||
}
|
||||
bs5={
|
||||
<Tag
|
||||
prepend={
|
||||
<i
|
||||
className="badge-tag-circle"
|
||||
style={{ backgroundColor: getTagColor(tag) }}
|
||||
/>
|
||||
}
|
||||
contentProps={{
|
||||
'aria-label': t('select_tag', { tagName: tag.name }),
|
||||
onClick: () => selectTag(tag._id),
|
||||
}}
|
||||
closeBtnProps={{
|
||||
onClick: () => handleRemoveTag(tag._id, projectId),
|
||||
}}
|
||||
className="ms-2"
|
||||
>
|
||||
{tag.name}
|
||||
</Tag>
|
||||
}
|
||||
/>
|
||||
contentProps={{
|
||||
'aria-label': t('select_tag', { tagName: tag.name }),
|
||||
onClick: () => selectTag(tag._id),
|
||||
}}
|
||||
closeBtnProps={{
|
||||
onClick: () => handleRemoveTag(tag._id, projectId),
|
||||
}}
|
||||
className="ms-2"
|
||||
>
|
||||
{tag.name}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../../../shared/components/icon'
|
||||
import { getOwnerName } from '../../../util/project'
|
||||
import { Project } from '../../../../../../../types/project/dashboard/api'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
|
||||
type LinkSharingIconProps = {
|
||||
@@ -28,21 +26,10 @@ function LinkSharingIcon({
|
||||
{/* OverlayTrigger won't fire unless icon is wrapped in a span */}
|
||||
<span className={className}>
|
||||
{prependSpace ? ' ' : ''}
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<Icon
|
||||
type="link"
|
||||
className="small"
|
||||
accessibilityLabel={t('link_sharing')}
|
||||
/>
|
||||
}
|
||||
bs5={
|
||||
<MaterialIcon
|
||||
type="link"
|
||||
className="align-text-bottom"
|
||||
accessibilityLabel={t('link_sharing')}
|
||||
/>
|
||||
}
|
||||
<MaterialIcon
|
||||
type="link"
|
||||
className="align-text-bottom"
|
||||
accessibilityLabel={t('link_sharing')}
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
|
||||
@@ -23,7 +23,6 @@ export const ProjectCheckbox = memo<{ projectId: string; projectName: string }>(
|
||||
checked={selectedProjectIds.has(projectId)}
|
||||
aria-label={t('select_project', { project: projectName })}
|
||||
data-project-id={projectId}
|
||||
bs3Props={{ bsClass: 'dash-cell-checkbox-wrapper' }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import { getOwnerName } from '../../util/project'
|
||||
import { Project } from '../../../../../../types/project/dashboard/api'
|
||||
import { ProjectCheckbox } from './project-checkbox'
|
||||
import { ProjectListOwnerName } from '@/features/project-list/components/table/project-list-owner-name'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
|
||||
type ProjectListTableRowProps = {
|
||||
project: Project
|
||||
@@ -19,79 +17,32 @@ function ProjectListTableRow({ project, selected }: ProjectListTableRowProps) {
|
||||
const ownerName = getOwnerName(project)
|
||||
|
||||
return (
|
||||
<tr className={selected ? bsVersion({ bs5: 'table-active' }) : undefined}>
|
||||
<td
|
||||
className={classnames(
|
||||
'dash-cell-checkbox',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-table-cell',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
>
|
||||
<tr className={selected ? 'table-active' : undefined}>
|
||||
<td className="dash-cell-checkbox d-none d-md-table-cell">
|
||||
<ProjectCheckbox projectId={project.id} projectName={project.name} />
|
||||
</td>
|
||||
<td className="dash-cell-name">
|
||||
<a href={`/project/${project.id}`}>{project.name}</a>{' '}
|
||||
<InlineTags
|
||||
className={bsVersion({
|
||||
bs5: 'd-none d-md-inline',
|
||||
bs3: 'hidden-xs',
|
||||
})}
|
||||
projectId={project.id}
|
||||
/>
|
||||
<InlineTags className="d-none d-md-inline" projectId={project.id} />
|
||||
</td>
|
||||
<td
|
||||
className={classnames(
|
||||
'dash-cell-date-owner',
|
||||
'pb-0',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
>
|
||||
<td className="dash-cell-date-owner pb-0 d-md-none">
|
||||
<LastUpdatedCell project={project} />
|
||||
{ownerName ? <ProjectListOwnerName ownerName={ownerName} /> : null}
|
||||
</td>
|
||||
<td
|
||||
className={classnames(
|
||||
'dash-cell-owner',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-table-cell',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
>
|
||||
<td className="dash-cell-owner d-none d-md-table-cell">
|
||||
<OwnerCell project={project} />
|
||||
</td>
|
||||
<td
|
||||
className={classnames(
|
||||
'dash-cell-date',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-table-cell',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
>
|
||||
<td className="dash-cell-date d-none d-md-table-cell">
|
||||
<LastUpdatedCell project={project} />
|
||||
</td>
|
||||
<td
|
||||
className={classnames(
|
||||
'dash-cell-tag',
|
||||
'pt-0',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
>
|
||||
<td className="dash-cell-tag pt-0 d-md-none">
|
||||
<InlineTags projectId={project.id} />
|
||||
</td>
|
||||
<td className="dash-cell-actions">
|
||||
<div
|
||||
className={bsVersion({
|
||||
bs5: 'd-none d-md-block',
|
||||
bs3: 'hidden-xs',
|
||||
})}
|
||||
>
|
||||
<div className="d-none d-md-block">
|
||||
<ActionsCell project={project} />
|
||||
</div>
|
||||
<div className={bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })}>
|
||||
<div className="d-md-none">
|
||||
<ActionsDropdown project={project} />
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useRef, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../../shared/components/icon'
|
||||
import ProjectListTableRow from './project-list-table-row'
|
||||
import { useProjectListContext } from '../../context/project-list-context'
|
||||
import useSort from '../../hooks/use-sort'
|
||||
@@ -8,30 +7,16 @@ import withContent, { SortBtnProps } from '../sort/with-content'
|
||||
import OLTable from '@/features/ui/components/ol/ol-table'
|
||||
import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
|
||||
function SortBtn({ onClick, text, iconType, screenReaderText }: SortBtnProps) {
|
||||
return (
|
||||
<button
|
||||
className={classnames(
|
||||
'table-header-sort-btn',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-inline-block',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
className="table-header-sort-btn d-none d-md-inline-block"
|
||||
onClick={onClick}
|
||||
aria-label={screenReaderText}
|
||||
>
|
||||
<span className={bsVersion({ bs3: 'tablesort-text' })}>{text}</span>
|
||||
{iconType && (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type={iconType} />}
|
||||
bs5={<MaterialIcon type={iconType} />}
|
||||
/>
|
||||
)}
|
||||
<span>{text}</span>
|
||||
{iconType && <MaterialIcon type={iconType} />}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -66,26 +51,11 @@ function ProjectListTable() {
|
||||
|
||||
return (
|
||||
<OLTable className="project-dash-table" container={false} hover>
|
||||
<caption
|
||||
className={bsVersion({ bs5: 'visually-hidden', bs3: 'sr-only' })}
|
||||
>
|
||||
{t('projects_list')}
|
||||
</caption>
|
||||
<thead
|
||||
className={bsVersion({
|
||||
bs5: 'visually-hidden-max-md',
|
||||
bs3: 'sr-only-xs',
|
||||
})}
|
||||
>
|
||||
<caption className="visually-hidden">{t('projects_list')}</caption>
|
||||
<thead className="visually-hidden-max-md">
|
||||
<tr>
|
||||
<th
|
||||
className={classnames(
|
||||
'dash-cell-checkbox',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-table-cell',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
className="dash-cell-checkbox d-none d-md-table-cell"
|
||||
aria-label={t('select_projects')}
|
||||
>
|
||||
<OLFormCheckbox
|
||||
@@ -97,10 +67,6 @@ function ProjectListTable() {
|
||||
}
|
||||
disabled={visibleProjects.length === 0}
|
||||
aria-label={t('select_all_projects')}
|
||||
bs3Props={{
|
||||
bsClass: 'dash-cell-checkbox-wrapper',
|
||||
inputRef: undefined,
|
||||
}}
|
||||
inputRef={checkAllRef}
|
||||
/>
|
||||
</th>
|
||||
@@ -123,22 +89,13 @@ function ProjectListTable() {
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
className={classnames(
|
||||
'dash-cell-date-owner',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
className="dash-cell-date-owner d-md-none"
|
||||
aria-label={t('date_and_owner')}
|
||||
>
|
||||
{t('date_and_owner')}
|
||||
</th>
|
||||
<th
|
||||
className={classnames(
|
||||
'dash-cell-owner',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-table-cell',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
className="dash-cell-owner d-none d-md-table-cell"
|
||||
aria-label={t('owner')}
|
||||
aria-sort={
|
||||
sort.by === 'owner'
|
||||
@@ -156,13 +113,7 @@ function ProjectListTable() {
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
className={classnames(
|
||||
'dash-cell-date',
|
||||
bsVersion({
|
||||
bs5: 'd-none d-md-table-cell',
|
||||
bs3: 'hidden-xs',
|
||||
})
|
||||
)}
|
||||
className="dash-cell-date d-none d-md-table-cell"
|
||||
aria-label={t('last_modified')}
|
||||
aria-sort={
|
||||
sort.by === 'lastUpdated'
|
||||
@@ -179,13 +130,7 @@ function ProjectListTable() {
|
||||
onClick={() => handleSort('lastUpdated')}
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
className={classnames(
|
||||
'dash-cell-tag',
|
||||
bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
|
||||
)}
|
||||
aria-label={t('tags')}
|
||||
>
|
||||
<th className="dash-cell-tag d-md-none" aria-label={t('tags')}>
|
||||
{t('tags')}
|
||||
</th>
|
||||
<th className="dash-cell-actions" aria-label={t('actions')}>
|
||||
|
||||
@@ -7,7 +7,6 @@ import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import { archiveProject } from '../../../../util/api'
|
||||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
function ArchiveProjectsButton() {
|
||||
const { selectedProjects, toggleSelectedProject, updateProjectViewData } =
|
||||
@@ -50,7 +49,7 @@ function ArchiveProjectsButton() {
|
||||
onClick={handleOpenModal}
|
||||
variant="secondary"
|
||||
accessibilityLabel={text}
|
||||
icon={bsVersion({ bs5: 'inbox', bs3: 'inbox' })}
|
||||
icon="inbox"
|
||||
/>
|
||||
</OLTooltip>
|
||||
<ArchiveProjectModal
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as eventTracking from '../../../../../../infrastructure/event-tracking'
|
||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import { useLocation } from '../../../../../../shared/hooks/use-location'
|
||||
import { isSmallDevice } from '../../../../../../infrastructure/event-tracking'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
function DownloadProjectsButton() {
|
||||
const { selectedProjects, selectOrUnselectAllProjects } =
|
||||
@@ -39,7 +38,7 @@ function DownloadProjectsButton() {
|
||||
onClick={handleDownloadProjects}
|
||||
variant="secondary"
|
||||
accessibilityLabel={text}
|
||||
icon={bsVersion({ bs5: 'download', bs3: 'cloud-download' })}
|
||||
icon="download"
|
||||
/>
|
||||
</OLTooltip>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { memo } from 'react'
|
||||
import { Dropdown as BS3Dropdown } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ControlledDropdown from '../../../../../../shared/components/controlled-dropdown'
|
||||
import CopyProjectMenuItem from '../menu-items/copy-project-menu-item'
|
||||
import RenameProjectMenuItem from '../menu-items/rename-project-menu-item'
|
||||
import {
|
||||
@@ -9,46 +7,24 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
function ProjectToolsMoreDropdownButton() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<ControlledDropdown id="project-tools-more-dropdown">
|
||||
<BS3Dropdown.Toggle bsStyle={null} className="btn-secondary">
|
||||
{t('more')}
|
||||
</BS3Dropdown.Toggle>
|
||||
<BS3Dropdown.Menu
|
||||
className="dropdown-menu-right"
|
||||
data-testid="project-tools-more-dropdown-menu"
|
||||
>
|
||||
<RenameProjectMenuItem />
|
||||
<CopyProjectMenuItem />
|
||||
</BS3Dropdown.Menu>
|
||||
</ControlledDropdown>
|
||||
}
|
||||
bs5={
|
||||
<Dropdown align="end">
|
||||
<DropdownToggle id="project-tools-more-dropdown" variant="secondary">
|
||||
{t('more')}
|
||||
</DropdownToggle>
|
||||
<DropdownMenu
|
||||
flip={false}
|
||||
data-testid="project-tools-more-dropdown-menu"
|
||||
>
|
||||
<li role="none">
|
||||
<RenameProjectMenuItem />
|
||||
</li>
|
||||
<li role="none">
|
||||
<CopyProjectMenuItem />
|
||||
</li>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
<Dropdown align="end">
|
||||
<DropdownToggle id="project-tools-more-dropdown" variant="secondary">
|
||||
{t('more')}
|
||||
</DropdownToggle>
|
||||
<DropdownMenu flip={false} data-testid="project-tools-more-dropdown-menu">
|
||||
<li role="none">
|
||||
<RenameProjectMenuItem />
|
||||
</li>
|
||||
<li role="none">
|
||||
<CopyProjectMenuItem />
|
||||
</li>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { sortBy } from 'lodash'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { Button, Dropdown as BS3Dropdown } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ControlledDropdown from '../../../../../../shared/components/controlled-dropdown'
|
||||
import Icon from '../../../../../../shared/components/icon'
|
||||
import MaterialIcon from '../../../../../../shared/components/material-icon'
|
||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import useTag from '../../../../hooks/use-tag'
|
||||
import { addProjectsToTag, removeProjectsFromTag } from '../../../../util/api'
|
||||
import { getTagColor } from '../../../../util/tag'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownDivider,
|
||||
@@ -79,140 +75,62 @@ function TagsDropdown() {
|
||||
[selectedProjects]
|
||||
)
|
||||
|
||||
const containsSomeSelectedProjects = useCallback(
|
||||
tag => {
|
||||
for (const project of selectedProjects) {
|
||||
if (tag.project_ids?.includes(project.id)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
[selectedProjects]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<ControlledDropdown id="tags">
|
||||
<BS3Dropdown.Toggle
|
||||
bsStyle={null}
|
||||
className="btn-secondary"
|
||||
aria-label={t('tags')}
|
||||
<Dropdown align="end" autoClose="outside">
|
||||
<DropdownToggle
|
||||
id="project-tools-more-dropdown"
|
||||
variant="secondary"
|
||||
aria-label={t('tags')}
|
||||
>
|
||||
<MaterialIcon type="label" className="align-text-top" />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu
|
||||
flip={false}
|
||||
data-testid="project-tools-more-dropdown-menu"
|
||||
>
|
||||
<DropdownHeader>{t('add_to_tag')}</DropdownHeader>
|
||||
{sortBy(tags, tag => tag.name?.toLowerCase()).map((tag, index) => (
|
||||
<li role="none" key={tag._id}>
|
||||
<DropdownItem
|
||||
onClick={e =>
|
||||
containsAllSelectedProjects(tag)
|
||||
? handleRemoveTagFromSelectedProjects(e, tag._id)
|
||||
: handleAddTagToSelectedProjects(e, tag._id)
|
||||
}
|
||||
aria-label={t('add_or_remove_project_from_tag', {
|
||||
tagName: tag.name,
|
||||
})}
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
leadingIcon={
|
||||
containsAllSelectedProjects(tag) ? (
|
||||
'check'
|
||||
) : (
|
||||
<DropdownItem.EmptyLeadingIcon />
|
||||
)
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="badge-tag-circle align-self-center ms-0"
|
||||
style={{ backgroundColor: getTagColor(tag) }}
|
||||
/>
|
||||
<span className="text-truncate">{tag.name}</span>
|
||||
</DropdownItem>
|
||||
</li>
|
||||
))}
|
||||
<DropdownDivider />
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
onClick={handleOpenCreateTagModal}
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<MaterialIcon type="label" style={{ verticalAlign: 'sub' }} />
|
||||
</BS3Dropdown.Toggle>
|
||||
<BS3Dropdown.Menu className="dropdown-menu-right">
|
||||
<li className="dropdown-header" role="heading" aria-level={3}>
|
||||
{t('add_to_tag')}
|
||||
</li>
|
||||
{sortBy(tags, tag => tag.name?.toLowerCase()).map(tag => {
|
||||
return (
|
||||
<li key={tag._id}>
|
||||
<Button
|
||||
className="tag-dropdown-button"
|
||||
onClick={e =>
|
||||
containsAllSelectedProjects(tag)
|
||||
? handleRemoveTagFromSelectedProjects(e, tag._id)
|
||||
: handleAddTagToSelectedProjects(e, tag._id)
|
||||
}
|
||||
aria-label={t('add_or_remove_project_from_tag', {
|
||||
tagName: tag.name,
|
||||
})}
|
||||
>
|
||||
<Icon
|
||||
type={
|
||||
containsAllSelectedProjects(tag)
|
||||
? 'check-square-o'
|
||||
: containsSomeSelectedProjects(tag)
|
||||
? 'minus-square-o'
|
||||
: 'square-o'
|
||||
}
|
||||
className="tag-checkbox"
|
||||
/>{' '}
|
||||
<span
|
||||
className="tag-dot"
|
||||
style={{
|
||||
backgroundColor: getTagColor(tag),
|
||||
}}
|
||||
/>{' '}
|
||||
{tag.name}
|
||||
</Button>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
<li className="divider" />
|
||||
<li>
|
||||
<Button
|
||||
className="tag-dropdown-button"
|
||||
onClick={handleOpenCreateTagModal}
|
||||
>
|
||||
{t('create_new_tag')}
|
||||
</Button>
|
||||
</li>
|
||||
</BS3Dropdown.Menu>
|
||||
</ControlledDropdown>
|
||||
}
|
||||
bs5={
|
||||
<Dropdown align="end" autoClose="outside">
|
||||
<DropdownToggle
|
||||
id="project-tools-more-dropdown"
|
||||
variant="secondary"
|
||||
aria-label={t('tags')}
|
||||
>
|
||||
<MaterialIcon type="label" className="align-text-top" />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu
|
||||
flip={false}
|
||||
data-testid="project-tools-more-dropdown-menu"
|
||||
>
|
||||
<DropdownHeader>{t('add_to_tag')}</DropdownHeader>
|
||||
{sortBy(tags, tag => tag.name?.toLowerCase()).map(
|
||||
(tag, index) => (
|
||||
<li role="none" key={tag._id}>
|
||||
<DropdownItem
|
||||
onClick={e =>
|
||||
containsAllSelectedProjects(tag)
|
||||
? handleRemoveTagFromSelectedProjects(e, tag._id)
|
||||
: handleAddTagToSelectedProjects(e, tag._id)
|
||||
}
|
||||
aria-label={t('add_or_remove_project_from_tag', {
|
||||
tagName: tag.name,
|
||||
})}
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
leadingIcon={
|
||||
containsAllSelectedProjects(tag) ? (
|
||||
'check'
|
||||
) : (
|
||||
<DropdownItem.EmptyLeadingIcon />
|
||||
)
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="badge-tag-circle align-self-center ms-0"
|
||||
style={{ backgroundColor: getTagColor(tag) }}
|
||||
/>
|
||||
<span className="text-truncate">{tag.name}</span>
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
<DropdownDivider />
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
onClick={handleOpenCreateTagModal}
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{t('create_new_tag')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
{t('create_new_tag')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<CreateTagModal id="toolbar-create-tag-modal" />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -7,7 +7,6 @@ import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import { trashProject } from '../../../../util/api'
|
||||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
|
||||
function TrashProjectsButton() {
|
||||
const { selectedProjects, toggleSelectedProject, updateProjectViewData } =
|
||||
@@ -50,7 +49,7 @@ function TrashProjectsButton() {
|
||||
onClick={handleOpenModal}
|
||||
variant="secondary"
|
||||
accessibilityLabel={text}
|
||||
icon={bsVersion({ bs5: 'delete', bs3: 'trash' })}
|
||||
icon="delete"
|
||||
/>
|
||||
</OLTooltip>
|
||||
<TrashProjectModal
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import MaterialIcon from '../../../shared/components/material-icon'
|
||||
import { getTagColor } from '../util/tag'
|
||||
import MenuItemButton from './dropdown/menu-item-button'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import {
|
||||
UNCATEGORIZED_KEY,
|
||||
useProjectListContext,
|
||||
@@ -12,7 +9,6 @@ import useTag from '../hooks/use-tag'
|
||||
import { sortBy } from 'lodash'
|
||||
import { Tag } from '../../../../../app/src/Features/Tags/types'
|
||||
import { DropdownItem } from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
type TagsListProps = {
|
||||
onTagClick?: () => void
|
||||
@@ -39,143 +35,63 @@ function TagsList({ onTagClick, onEditClick }: TagsListProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<>
|
||||
{sortBy(tags, ['name']).map((tag, index) => (
|
||||
<MenuItemButton
|
||||
key={index}
|
||||
onClick={e =>
|
||||
handleClick(e as unknown as React.MouseEvent, tag)
|
||||
}
|
||||
className="projects-types-menu-item projects-types-menu-tag-item"
|
||||
afterNode={
|
||||
<Button
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleManageTag(e, tag._id)
|
||||
onEditClick?.()
|
||||
}}
|
||||
className="btn-transparent edit-btn me-2"
|
||||
bsStyle={null}
|
||||
>
|
||||
<Icon type="pencil" fw />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<span className="tag-item menu-item-button-text">
|
||||
{selectedTagId === tag._id ? (
|
||||
<Icon type="check" className="menu-item-button-icon" />
|
||||
) : null}
|
||||
<span
|
||||
className="me-2"
|
||||
style={{
|
||||
color: getTagColor(tag),
|
||||
}}
|
||||
>
|
||||
<MaterialIcon
|
||||
type="label"
|
||||
style={{ verticalAlign: 'sub' }}
|
||||
/>
|
||||
</span>
|
||||
<span className="tag-name-and-size">
|
||||
<span className="tag-name">{tag.name}</span>
|
||||
<span className="subdued">({tag.project_ids?.length})</span>
|
||||
</span>
|
||||
<>
|
||||
{sortBy(tags, ['name']).map((tag, index) => (
|
||||
<li role="none" className="position-relative" key={index}>
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={e => handleClick(e as unknown as React.MouseEvent, tag)}
|
||||
leadingIcon={
|
||||
<span style={{ color: getTagColor(tag) }}>
|
||||
<MaterialIcon type="label" className="align-text-top" />
|
||||
</span>
|
||||
</MenuItemButton>
|
||||
))}
|
||||
<MenuItemButton
|
||||
className="untagged projects-types-menu-item"
|
||||
onClick={() => {
|
||||
selectTag(UNCATEGORIZED_KEY)
|
||||
onTagClick?.()
|
||||
}}
|
||||
}
|
||||
trailingIcon={selectedTagId === tag._id ? 'check' : undefined}
|
||||
active={selectedTagId === tag._id}
|
||||
>
|
||||
{selectedTagId === UNCATEGORIZED_KEY ? (
|
||||
<Icon type="check" className="menu-item-button-icon" />
|
||||
) : null}
|
||||
<span className="tag-item menu-item-button-text">
|
||||
{t('uncategorized')}
|
||||
<span className="subdued">({untaggedProjectsCount})</span>
|
||||
<span className="project-menu-item-tag-name text-truncate">
|
||||
{tag.name} ({tag.project_ids?.length})
|
||||
</span>
|
||||
</MenuItemButton>
|
||||
<MenuItemButton
|
||||
onClick={() => {
|
||||
openCreateTagModal()
|
||||
onTagClick?.()
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
className="project-menu-item-edit-btn"
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleManageTag(e, tag._id)
|
||||
}}
|
||||
className="projects-types-menu-item"
|
||||
aria-label={t('edit_tag')}
|
||||
>
|
||||
<span className="tag-item menu-item-button-text">
|
||||
<Icon type="plus" className="me-2" />
|
||||
<span>{t('new_tag')}</span>
|
||||
</span>
|
||||
</MenuItemButton>
|
||||
</>
|
||||
}
|
||||
bs5={
|
||||
<>
|
||||
{sortBy(tags, ['name']).map((tag, index) => (
|
||||
<li role="none" className="position-relative" key={index}>
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={e =>
|
||||
handleClick(e as unknown as React.MouseEvent, tag)
|
||||
}
|
||||
leadingIcon={
|
||||
<span style={{ color: getTagColor(tag) }}>
|
||||
<MaterialIcon type="label" className="align-text-top" />
|
||||
</span>
|
||||
}
|
||||
trailingIcon={selectedTagId === tag._id ? 'check' : undefined}
|
||||
active={selectedTagId === tag._id}
|
||||
>
|
||||
<span className="project-menu-item-tag-name text-truncate">
|
||||
{tag.name} ({tag.project_ids?.length})
|
||||
</span>
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
className="project-menu-item-edit-btn"
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleManageTag(e, tag._id)
|
||||
}}
|
||||
aria-label={t('edit_tag')}
|
||||
>
|
||||
<MaterialIcon type="edit" className="align-text-top" />
|
||||
</DropdownItem>
|
||||
</li>
|
||||
))}
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={() => selectTag(UNCATEGORIZED_KEY)}
|
||||
trailingIcon={
|
||||
selectedTagId === UNCATEGORIZED_KEY ? 'check' : undefined
|
||||
}
|
||||
active={selectedTagId === UNCATEGORIZED_KEY}
|
||||
>
|
||||
{t('uncategorized')} ({untaggedProjectsCount})
|
||||
</DropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={openCreateTagModal}
|
||||
leadingIcon="add"
|
||||
>
|
||||
{t('new_tag')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<MaterialIcon type="edit" className="align-text-top" />
|
||||
</DropdownItem>
|
||||
</li>
|
||||
))}
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={() => selectTag(UNCATEGORIZED_KEY)}
|
||||
trailingIcon={
|
||||
selectedTagId === UNCATEGORIZED_KEY ? 'check' : undefined
|
||||
}
|
||||
active={selectedTagId === UNCATEGORIZED_KEY}
|
||||
>
|
||||
{t('uncategorized')} ({untaggedProjectsCount})
|
||||
</DropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={openCreateTagModal}
|
||||
leadingIcon="add"
|
||||
>
|
||||
{t('new_tag')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
</>
|
||||
<CreateTagModal id="create-tag-modal-dropdown" disableCustomColor />
|
||||
<ManageTagModal id="manage-tag-modal-dropdown" />
|
||||
</>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useState, forwardRef } from 'react'
|
||||
import { useCallback, forwardRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { sendMB } from '../../../../infrastructure/event-tracking'
|
||||
import getMeta from '../../../../utils/meta'
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
const CustomDropdownToggle = forwardRef<
|
||||
HTMLButtonElement,
|
||||
@@ -57,36 +56,11 @@ type WelcomeMessageCreateNewProjectDropdownProps = {
|
||||
function WelcomeMessageCreateNewProjectDropdown({
|
||||
setActiveModal,
|
||||
}: WelcomeMessageCreateNewProjectDropdownProps) {
|
||||
const [showDropdown, setShowDropdown] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
const portalTemplates = getMeta('ol-portalTemplates') || []
|
||||
|
||||
const { isOverleaf } = getMeta('ol-ExposedSettings')
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
sendMB('welcome-page-create-first-project-click', {
|
||||
dropdownMenu: 'main-button',
|
||||
dropdownOpen: showDropdown,
|
||||
})
|
||||
|
||||
// toggle the dropdown
|
||||
setShowDropdown(!showDropdown)
|
||||
}, [setShowDropdown, showDropdown])
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (e.code === 'Enter') {
|
||||
handleClick()
|
||||
} else if (e.code === 'Space') {
|
||||
handleClick()
|
||||
|
||||
// prevent page down when pressing space
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
[handleClick]
|
||||
)
|
||||
|
||||
const handleDropdownItemClick = useCallback(
|
||||
(
|
||||
e: React.MouseEvent,
|
||||
@@ -96,15 +70,13 @@ function WelcomeMessageCreateNewProjectDropdown({
|
||||
// prevent firing the main dropdown onClick event
|
||||
e.stopPropagation()
|
||||
|
||||
setShowDropdown(false)
|
||||
|
||||
sendMB('welcome-page-create-first-project-click', {
|
||||
dropdownOpen: true,
|
||||
dropdownMenu: dropdownMenuEvent,
|
||||
})
|
||||
setActiveModal(modalVariant)
|
||||
},
|
||||
[setActiveModal, setShowDropdown]
|
||||
[setActiveModal]
|
||||
)
|
||||
|
||||
const handlePortalTemplateClick = useCallback(
|
||||
@@ -112,187 +84,91 @@ function WelcomeMessageCreateNewProjectDropdown({
|
||||
// prevent firing the main dropdown onClick event
|
||||
e.stopPropagation()
|
||||
|
||||
setShowDropdown(false)
|
||||
|
||||
sendMB('welcome-page-create-first-project-click', {
|
||||
dropdownMenu: 'institution-template',
|
||||
dropdownOpen: true,
|
||||
institutionTemplateName,
|
||||
})
|
||||
},
|
||||
[setShowDropdown]
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<div className="welcome-message-card-item">
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="card welcome-message-card"
|
||||
onClick={handleClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
<Dropdown>
|
||||
<DropdownToggle
|
||||
as={CustomDropdownToggle}
|
||||
id="create-new-project-dropdown-toggle-btn"
|
||||
/>
|
||||
<DropdownMenu flip={false} className="create-new-project-dropdown">
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
onClick={e =>
|
||||
handleDropdownItemClick(e, 'blank_project', 'blank-project')
|
||||
}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<p>{t('create_a_new_project')}</p>
|
||||
<img
|
||||
className="welcome-message-card-img"
|
||||
src="/img/welcome-page/create-a-new-project.svg"
|
||||
aria-hidden="true"
|
||||
alt=""
|
||||
/>
|
||||
{showDropdown && (
|
||||
<div className="card create-new-project-dropdown">
|
||||
<button
|
||||
onClick={e =>
|
||||
handleDropdownItemClick(e, 'blank_project', 'blank-project')
|
||||
}
|
||||
>
|
||||
{t('blank_project')}
|
||||
</button>
|
||||
<button
|
||||
onClick={e =>
|
||||
handleDropdownItemClick(
|
||||
e,
|
||||
'example_project',
|
||||
'example-project'
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('example_project')}
|
||||
</button>
|
||||
<button
|
||||
onClick={e =>
|
||||
handleDropdownItemClick(
|
||||
e,
|
||||
'upload_project',
|
||||
'upload-project'
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('upload_project')}
|
||||
</button>
|
||||
{isOverleaf && (
|
||||
<button
|
||||
onClick={e =>
|
||||
handleDropdownItemClick(
|
||||
e,
|
||||
'import_from_github',
|
||||
'import-from-github'
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('import_from_github')}
|
||||
</button>
|
||||
)}
|
||||
{portalTemplates.length > 0 ? (
|
||||
<>
|
||||
<hr />
|
||||
<div className="dropdown-header">
|
||||
{t('institution_templates')}
|
||||
</div>
|
||||
{portalTemplates?.map((portalTemplate, index) => (
|
||||
<a
|
||||
key={`portal-template-${index}`}
|
||||
href={`${portalTemplate.url}#templates`}
|
||||
onClick={e =>
|
||||
handlePortalTemplateClick(e, portalTemplate.name)
|
||||
}
|
||||
>
|
||||
{portalTemplate.name}
|
||||
</a>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
bs5={
|
||||
<Dropdown className="welcome-message-card-item">
|
||||
<DropdownToggle
|
||||
as={CustomDropdownToggle}
|
||||
id="create-new-project-dropdown-toggle-btn"
|
||||
/>
|
||||
<DropdownMenu flip={false} className="create-new-project-dropdown">
|
||||
<li role="none">
|
||||
{t('blank_project')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
onClick={e =>
|
||||
handleDropdownItemClick(e, 'example_project', 'example-project')
|
||||
}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{t('example_project')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
onClick={e =>
|
||||
handleDropdownItemClick(e, 'upload_project', 'upload-project')
|
||||
}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{t('upload_project')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
{isOverleaf && (
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
onClick={e =>
|
||||
handleDropdownItemClick(
|
||||
e,
|
||||
'import_from_github',
|
||||
'import-from-github'
|
||||
)
|
||||
}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{t('import_from_github')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)}
|
||||
{(portalTemplates?.length ?? 0) > 0 ? (
|
||||
<>
|
||||
<DropdownDivider />
|
||||
<DropdownHeader aria-hidden="true">
|
||||
{t('institution_templates')}
|
||||
</DropdownHeader>
|
||||
{portalTemplates?.map((portalTemplate, index) => (
|
||||
<DropdownItem
|
||||
as="button"
|
||||
onClick={e =>
|
||||
handleDropdownItemClick(e, 'blank_project', 'blank-project')
|
||||
}
|
||||
tabIndex={-1}
|
||||
key={`portal-template-${index}`}
|
||||
onClick={e => handlePortalTemplateClick(e, portalTemplate.name)}
|
||||
href={`${portalTemplate.url}#templates`}
|
||||
>
|
||||
{t('blank_project')}
|
||||
{portalTemplate.name}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
onClick={e =>
|
||||
handleDropdownItemClick(
|
||||
e,
|
||||
'example_project',
|
||||
'example-project'
|
||||
)
|
||||
}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{t('example_project')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
onClick={e =>
|
||||
handleDropdownItemClick(e, 'upload_project', 'upload-project')
|
||||
}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{t('upload_project')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
{isOverleaf && (
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
onClick={e =>
|
||||
handleDropdownItemClick(
|
||||
e,
|
||||
'import_from_github',
|
||||
'import-from-github'
|
||||
)
|
||||
}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{t('import_from_github')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)}
|
||||
{(portalTemplates?.length ?? 0) > 0 ? (
|
||||
<>
|
||||
<DropdownDivider />
|
||||
<DropdownHeader aria-hidden="true">
|
||||
{t('institution_templates')}
|
||||
</DropdownHeader>
|
||||
{portalTemplates?.map((portalTemplate, index) => (
|
||||
<DropdownItem
|
||||
key={`portal-template-${index}`}
|
||||
onClick={e =>
|
||||
handlePortalTemplateClick(e, portalTemplate.name)
|
||||
}
|
||||
href={`${portalTemplate.url}#templates`}
|
||||
>
|
||||
{portalTemplate.name}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ describe('<NewProjectButton />', function () {
|
||||
|
||||
// dynamic menu based on portalTemplates
|
||||
const affiliationTemplate = screen.getByRole('menuitem', {
|
||||
name: 'Affiliation 1',
|
||||
name: 'Affiliation 1 Template',
|
||||
})
|
||||
expect(affiliationTemplate.getAttribute('href')).to.equal(
|
||||
'/edu/test-new-template#templates'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import sinon, { SinonStub } from 'sinon'
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
individualSubscription,
|
||||
} from '../fixtures/user-subscriptions'
|
||||
import getMeta from '@/utils/meta'
|
||||
import * as bootstrapUtils from '@/features/utils/bootstrap-5'
|
||||
|
||||
const renderWithinProjectListProvider = (Component: React.ComponentType) => {
|
||||
render(<Component />, {
|
||||
@@ -65,6 +66,16 @@ describe('<UserNotifications />', function () {
|
||||
appName: 'Overleaf',
|
||||
}
|
||||
|
||||
let isBootstrap5Stub: SinonStub
|
||||
|
||||
before(function () {
|
||||
isBootstrap5Stub = sinon.stub(bootstrapUtils, 'isBootstrap5').returns(true)
|
||||
})
|
||||
|
||||
after(function () {
|
||||
isBootstrap5Stub.restore()
|
||||
})
|
||||
|
||||
beforeEach(function () {
|
||||
fetchMock.reset()
|
||||
|
||||
@@ -127,15 +138,15 @@ describe('<UserNotifications />', function () {
|
||||
|
||||
expect(joinBtn.disabled).to.be.true
|
||||
|
||||
await waitForElementToBeRemoved(() =>
|
||||
screen.getByRole('button', { name: /joining/i })
|
||||
)
|
||||
screen.debug()
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByText('Loading'))
|
||||
|
||||
expect(acceptMock.called()).to.be.true
|
||||
screen.getByText(/joined/i)
|
||||
expect(screen.queryByRole('button', { name: /join project/i })).to.be.null
|
||||
|
||||
const openProject = screen.getByRole('link', { name: /open project/i })
|
||||
const openProject = screen.getByRole('button', { name: /open project/i })
|
||||
expect(openProject.getAttribute('href')).to.equal(
|
||||
`/project/${notificationProjectInvite.messageOpts.projectId}`
|
||||
)
|
||||
@@ -173,12 +184,12 @@ describe('<UserNotifications />', function () {
|
||||
fireEvent.click(joinBtn)
|
||||
|
||||
await waitForElementToBeRemoved(() =>
|
||||
screen.getByRole('button', { name: /joining/i })
|
||||
screen.getByRole('button', { name: /loading/i })
|
||||
)
|
||||
|
||||
expect(fetchMock.called()).to.be.true
|
||||
screen.getByRole('button', { name: /join project/i })
|
||||
expect(screen.queryByRole('link', { name: /open project/i })).to.be.null
|
||||
expect(screen.queryByRole('button', { name: /open project/i })).to.be.null
|
||||
})
|
||||
|
||||
it('shows WFH2020', async function () {
|
||||
@@ -197,7 +208,7 @@ describe('<UserNotifications />', function () {
|
||||
screen.getByRole('alert')
|
||||
screen.getByText(/your free WFH2020 upgrade came to an end on/i)
|
||||
|
||||
const viewLink = screen.getByRole('link', { name: /view/i })
|
||||
const viewLink = screen.getByRole('button', { name: /view/i })
|
||||
expect(viewLink.getAttribute('href')).to.equal(
|
||||
'https://www.overleaf.com/events/wfh2020'
|
||||
)
|
||||
@@ -236,7 +247,7 @@ describe('<UserNotifications />', function () {
|
||||
expect(findOutMore.getAttribute('href')).to.equal(
|
||||
'https://www.overleaf.com/learn/how-to/Institutional_Login'
|
||||
)
|
||||
const linkAccount = screen.getByRole('link', { name: /link account/i })
|
||||
const linkAccount = screen.getByRole('button', { name: /link account/i })
|
||||
expect(linkAccount.getAttribute('href')).to.equal(
|
||||
`${exposedSettings.samlInitPath}?university_id=${notificationIPMatchedAffiliation.messageOpts.institutionId}&auto=/project`
|
||||
)
|
||||
@@ -271,7 +282,7 @@ describe('<UserNotifications />', function () {
|
||||
/add an institutional email address to claim your features/i
|
||||
)
|
||||
|
||||
const addAffiliation = screen.getByRole('link', {
|
||||
const addAffiliation = screen.getByRole('button', {
|
||||
name: /add affiliation/i,
|
||||
})
|
||||
expect(addAffiliation.getAttribute('href')).to.equal(`/user/settings`)
|
||||
@@ -297,7 +308,7 @@ describe('<UserNotifications />', function () {
|
||||
screen.getByText(/file limit/i)
|
||||
screen.getByText(/You can't add more files to the project or sync it/i)
|
||||
|
||||
const accountSettings = screen.getByRole('link', {
|
||||
const accountSettings = screen.getByRole('button', {
|
||||
name: /Open project/i,
|
||||
})
|
||||
expect(accountSettings.getAttribute('href')).to.equal('/project/123')
|
||||
@@ -487,7 +498,7 @@ describe('<UserNotifications />', function () {
|
||||
'/learn/how-to/Institutional_Login'
|
||||
)
|
||||
|
||||
const action = screen.getByRole('link', { name: /link account/i })
|
||||
const action = screen.getByRole('button', { name: /link account/i })
|
||||
expect(action.getAttribute('href')).to.equal(
|
||||
`${exposedSettings.samlInitPath}?university_id=${notificationsInstitution.institutionId}&auto=/project&email=${notificationsInstitution.email}`
|
||||
)
|
||||
@@ -552,7 +563,7 @@ describe('<UserNotifications />', function () {
|
||||
screen.getByRole('alert')
|
||||
screen.getByText(/which is already registered with/i)
|
||||
|
||||
const action = screen.getByRole('link', { name: /find out more/i })
|
||||
const action = screen.getByRole('button', { name: /find out more/i })
|
||||
expect(action.getAttribute('href')).to.equal(
|
||||
'/learn/how-to/Institutional_Login'
|
||||
)
|
||||
@@ -735,7 +746,7 @@ describe('<UserNotifications />', function () {
|
||||
screen.getByRole('button', { name: /confirm affiliation/i })
|
||||
)
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByText(/sending/i))
|
||||
await waitForElementToBeRemoved(() => screen.getByText(/loading/i))
|
||||
screen.getByText(/check your email inbox to confirm/i)
|
||||
expect(screen.queryByRole('button', { name: /confirm affiliation/i })).to
|
||||
.be.null
|
||||
@@ -745,7 +756,7 @@ describe('<UserNotifications />', function () {
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: /resend confirmation email/i })
|
||||
)
|
||||
await waitForElementToBeRemoved(() => screen.getByText(/sending/i))
|
||||
await waitForElementToBeRemoved(() => screen.getByText('Loading'))
|
||||
expect(sendReconfirmationMock.calls()).to.have.lengthOf(2)
|
||||
})
|
||||
|
||||
@@ -814,7 +825,7 @@ describe('<UserNotifications />', function () {
|
||||
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
|
||||
await fetchMock.flush(true)
|
||||
|
||||
expect(screen.queryByRole('link', { name: 'Contact Sales' })).to.be.null
|
||||
expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.be.null
|
||||
})
|
||||
|
||||
it('shows the banner for users that have dismissed the previous banners', async function () {
|
||||
@@ -824,7 +835,7 @@ describe('<UserNotifications />', function () {
|
||||
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
|
||||
await fetchMock.flush(true)
|
||||
|
||||
expect(screen.queryByRole('link', { name: 'Contact Sales' })).to.not.be
|
||||
expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.not.be
|
||||
.null
|
||||
})
|
||||
|
||||
@@ -840,7 +851,7 @@ describe('<UserNotifications />', function () {
|
||||
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
|
||||
await fetchMock.flush(true)
|
||||
|
||||
expect(screen.queryByRole('link', { name: 'Contact Sales' })).to.not.be
|
||||
expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.not.be
|
||||
.null
|
||||
})
|
||||
|
||||
@@ -856,7 +867,7 @@ describe('<UserNotifications />', function () {
|
||||
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
|
||||
await fetchMock.flush(true)
|
||||
|
||||
expect(screen.queryByRole('link', { name: 'Contact Sales' })).to.be.null
|
||||
expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.be.null
|
||||
})
|
||||
|
||||
describe('users that are not in group and are not affiliated', function () {
|
||||
@@ -897,7 +908,7 @@ describe('<UserNotifications />', function () {
|
||||
screen.getByText(
|
||||
'Overleaf On-Premises: Does your company want to keep its data within its firewall? Overleaf offers Server Pro, an on-premises solution for companies. Get in touch to learn more.'
|
||||
)
|
||||
const link = screen.getByRole('link', { name: 'Contact Sales' })
|
||||
const link = screen.getByRole('button', { name: 'Contact Sales' })
|
||||
|
||||
expect(link.getAttribute('href')).to.equal(`/for/contact-sales-2`)
|
||||
})
|
||||
@@ -914,7 +925,7 @@ describe('<UserNotifications />', function () {
|
||||
screen.getByText(
|
||||
'Why do Fortune 500 companies and top research institutions trust Overleaf to streamline their collaboration? Get in touch to learn more.'
|
||||
)
|
||||
const link = screen.getByRole('link', { name: 'Contact Sales' })
|
||||
const link = screen.getByRole('button', { name: 'Contact Sales' })
|
||||
|
||||
expect(link.getAttribute('href')).to.equal(`/for/contact-sales-4`)
|
||||
})
|
||||
@@ -941,7 +952,7 @@ describe('<UserNotifications />', function () {
|
||||
})
|
||||
it('shows the banner', function () {
|
||||
renderWithinProjectListProvider(UserNotifications)
|
||||
const ctaLink = screen.getByRole('link', {
|
||||
const ctaLink = screen.getByRole('button', {
|
||||
name: 'Get Writefull Premium',
|
||||
})
|
||||
expect(ctaLink.getAttribute('href')).to.equal(
|
||||
@@ -951,7 +962,7 @@ describe('<UserNotifications />', function () {
|
||||
|
||||
it('dismisses the banner when the close button is clicked', function () {
|
||||
renderWithinProjectListProvider(UserNotifications)
|
||||
screen.getByRole('link', { name: /Writefull/ })
|
||||
screen.getByRole('button', { name: /Writefull/ })
|
||||
const WritefullPromoBanner = screen.getByTestId(
|
||||
'writefull-premium-promo-banner'
|
||||
)
|
||||
|
||||
@@ -158,14 +158,12 @@ describe('<TagsList />', function () {
|
||||
describe('Edit modal', function () {
|
||||
beforeEach(async function () {
|
||||
const tag1Button = screen.getByText('Tag 1')
|
||||
|
||||
const editButton = within(
|
||||
const dropdownToggle = within(
|
||||
tag1Button.closest('li') as HTMLElement
|
||||
).getByRole('button', {
|
||||
name: 'Edit',
|
||||
})
|
||||
|
||||
await fireEvent.click(editButton)
|
||||
).getByTestId('tag-dropdown-toggle')
|
||||
await fireEvent.click(dropdownToggle)
|
||||
const editMenuItem = await screen.findByRole('menuitem', { name: 'Edit' })
|
||||
await fireEvent.click(editMenuItem)
|
||||
})
|
||||
|
||||
it('modal is open', async function () {
|
||||
@@ -250,15 +248,15 @@ describe('<TagsList />', function () {
|
||||
|
||||
describe('Delete modal', function () {
|
||||
beforeEach(async function () {
|
||||
const tag1Button = screen.getByText('Another tag')
|
||||
|
||||
const deleteButton = within(
|
||||
const tag1Button = screen.getByText('Tag 1')
|
||||
const dropdownToggle = within(
|
||||
tag1Button.closest('li') as HTMLElement
|
||||
).getByRole('button', {
|
||||
).getByTestId('tag-dropdown-toggle')
|
||||
await fireEvent.click(dropdownToggle)
|
||||
const deleteMenuItem = await screen.findByRole('menuitem', {
|
||||
name: 'Delete',
|
||||
})
|
||||
|
||||
await fireEvent.click(deleteButton)
|
||||
await fireEvent.click(deleteMenuItem)
|
||||
})
|
||||
|
||||
it('modal is open', async function () {
|
||||
|
||||
@@ -11,13 +11,22 @@ describe('<ProjectTools />', function () {
|
||||
resetProjectListContextFetch()
|
||||
})
|
||||
|
||||
it('renders the project tools for the all projects filter', function () {
|
||||
it('renders the project tools for the all projects filter', async function () {
|
||||
renderWithProjectListContext(<ProjectTools />)
|
||||
expect(screen.getAllByRole('button').length).to.equal(5)
|
||||
screen.getByLabelText('Download')
|
||||
screen.getByLabelText('Archive')
|
||||
screen.getByLabelText('Trash')
|
||||
screen.getByLabelText('Tags')
|
||||
screen.getByRole('button', { name: 'Create new tag' })
|
||||
|
||||
const initialButtons = screen.getAllByRole('button')
|
||||
expect(initialButtons).to.have.length(4)
|
||||
|
||||
expect(screen.getByLabelText('Download')).to.exist
|
||||
expect(screen.getByLabelText('Archive')).to.exist
|
||||
expect(screen.getByLabelText('Trash')).to.exist
|
||||
expect(screen.getByLabelText('Tags')).to.exist
|
||||
|
||||
expect(screen.queryByText('Create new tag')).to.not.exist
|
||||
|
||||
screen.getByLabelText('Tags').click()
|
||||
|
||||
const createTagButton = await screen.findByText('Create new tag')
|
||||
expect(createTagButton).to.exist
|
||||
})
|
||||
})
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('<WelcomeMessage />', function () {
|
||||
screen.getByText('Institution Templates')
|
||||
|
||||
// dynamic menu based on portalTemplates
|
||||
const affiliationTemplate = screen.getByRole('link', {
|
||||
const affiliationTemplate = screen.getByRole('menuitem', {
|
||||
name: 'Affiliation 1',
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user