Library: Design Changes (#33933)
GitOrigin-RevId: b45ea92adc424e2864e952cd7f157509e10ffb7d
This commit is contained in:
@@ -96,6 +96,7 @@
|
||||
"add_ons": "",
|
||||
"add_or_remove_project_from_tag": "",
|
||||
"add_reference": "",
|
||||
"add_references": "",
|
||||
"add_role_and_department": "",
|
||||
"add_to_dictionary": "",
|
||||
"add_to_tag": "",
|
||||
@@ -1157,6 +1158,7 @@
|
||||
"manage_template": "",
|
||||
"manage_users_subtext": "",
|
||||
"manage_your_ai_assist_add_on": "",
|
||||
"manage_your_references_across_projects": "",
|
||||
"managed": "",
|
||||
"managed_user_accounts": "",
|
||||
"managed_user_invite_has_been_sent_to_email": "",
|
||||
@@ -1910,6 +1912,7 @@
|
||||
"start_typing_find_your_company": "",
|
||||
"start_typing_find_your_organization": "",
|
||||
"start_typing_find_your_university": "",
|
||||
"start_your_collection_of_most_used_references": "",
|
||||
"stop": "",
|
||||
"stop_compile": "",
|
||||
"stop_on_first_error": "",
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { DropdownItem } from '@/shared/components/dropdown/dropdown-menu'
|
||||
|
||||
type Props = {
|
||||
activePage: 'library' | 'projects'
|
||||
onProjectsClick?: () => void
|
||||
}
|
||||
|
||||
function MobilePageSwitcherItems({ activePage, onProjectsClick }: Props) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<>
|
||||
<li role="none">
|
||||
<DropdownItem href="/library" active={activePage === 'library'}>
|
||||
{t('library')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
{onProjectsClick ? (
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
trailingIcon="chevron_right"
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
onProjectsClick()
|
||||
}}
|
||||
>
|
||||
{t('projects')}
|
||||
</DropdownItem>
|
||||
) : (
|
||||
<DropdownItem href="/project" active={activePage === 'projects'}>
|
||||
{t('projects')}
|
||||
</DropdownItem>
|
||||
)}
|
||||
</li>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MobilePageSwitcherItems
|
||||
+64
-18
@@ -12,8 +12,11 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
} from '@/shared/components/dropdown/dropdown-menu'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
||||
import ProjectsFilterMenu from '../projects-filter-menu'
|
||||
import TagsList from '../tags-list'
|
||||
import MobilePageSwitcherItems from './mobile-page-switcher-items'
|
||||
|
||||
type ItemProps = {
|
||||
filter: Filter
|
||||
@@ -48,7 +51,9 @@ export function Item({ filter, text, onClick }: ItemProps) {
|
||||
function ProjectsDropdown() {
|
||||
const { t } = useTranslation()
|
||||
const [title, setTitle] = useState(() => t('all_projects'))
|
||||
const [view, setView] = useState<'top' | 'submenu'>('submenu')
|
||||
const { filter, selectedTagId, tags } = useProjectListContext()
|
||||
const isLibraryEnabled = isSplitTestEnabled('overleaf-library')
|
||||
const filterTranslations = useRef<Record<Filter, string>>({
|
||||
all: t('all_projects'),
|
||||
owned: t('your_projects'),
|
||||
@@ -73,8 +78,40 @@ function ProjectsDropdown() {
|
||||
}
|
||||
}, [filter, tags, selectedTagId, t])
|
||||
|
||||
const submenuItems = (
|
||||
<>
|
||||
<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 />
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<Dropdown>
|
||||
<Dropdown
|
||||
onToggle={
|
||||
isLibraryEnabled
|
||||
? show => {
|
||||
if (!show) {
|
||||
setView('submenu')
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<DropdownToggle
|
||||
id="projects-types-dropdown-toggle-btn"
|
||||
className="ps-0 mb-0 btn-transparent h3"
|
||||
@@ -86,23 +123,32 @@ function ProjectsDropdown() {
|
||||
</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 />
|
||||
{!isLibraryEnabled && submenuItems}
|
||||
{isLibraryEnabled && view === 'submenu' && (
|
||||
<>
|
||||
<li role="none">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
leadingIcon={<MaterialIcon type="chevron_left" />}
|
||||
aria-label={t('back')}
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
setView('top')
|
||||
}}
|
||||
>
|
||||
{t('projects')}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
{submenuItems}
|
||||
</>
|
||||
)}
|
||||
{isLibraryEnabled && view === 'top' && (
|
||||
<MobilePageSwitcherItems
|
||||
activePage="projects"
|
||||
onProjectsClick={() => setView('submenu')}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
)
|
||||
|
||||
@@ -21,6 +21,8 @@ import { useSendProjectListMB } from '@/features/project-list/components/project
|
||||
import type { PortalTemplate } from '../../../../../types/portal-template'
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { useProjectListContext } from '@/features/project-list/context/project-list-context'
|
||||
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
||||
|
||||
type SendTrackingEvent = {
|
||||
dropdownMenu: string
|
||||
@@ -65,6 +67,12 @@ function NewProjectButton({
|
||||
const markdownImportEnabled =
|
||||
useFeatureFlag('import-markdown') &&
|
||||
getMeta('ol-ExposedSettings').enablePandocConversions
|
||||
const { selectedTagId, tags } = useProjectListContext()
|
||||
const isLibraryEnabled = isSplitTestEnabled('overleaf-library')
|
||||
const initialTags =
|
||||
isLibraryEnabled && selectedTagId
|
||||
? tags.filter(tag => tag._id === selectedTagId)
|
||||
: []
|
||||
const sendTrackingEvent = useCallback(
|
||||
({
|
||||
dropdownMenu,
|
||||
@@ -310,7 +318,11 @@ function NewProjectButton({
|
||||
) : null}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<NewProjectButtonModal modal={modal} onHide={() => setModal(null)} />
|
||||
<NewProjectButtonModal
|
||||
modal={modal}
|
||||
onHide={() => setModal(null)}
|
||||
initialTags={initialTags}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+4
-2
@@ -1,11 +1,13 @@
|
||||
import ModalContentNewProjectForm from './modal-content-new-project-form'
|
||||
import { OLModal } from '@/shared/components/ol/ol-modal'
|
||||
import { Tag } from '../../../../../../app/src/Features/Tags/types'
|
||||
|
||||
type BlankProjectModalProps = {
|
||||
onHide: () => void
|
||||
initialTags?: Tag[]
|
||||
}
|
||||
|
||||
function BlankProjectModal({ onHide }: BlankProjectModalProps) {
|
||||
function BlankProjectModal({ onHide, initialTags }: BlankProjectModalProps) {
|
||||
return (
|
||||
<OLModal
|
||||
show
|
||||
@@ -14,7 +16,7 @@ function BlankProjectModal({ onHide }: BlankProjectModalProps) {
|
||||
id="blank-project-modal"
|
||||
backdrop="static"
|
||||
>
|
||||
<ModalContentNewProjectForm onCancel={onHide} />
|
||||
<ModalContentNewProjectForm onCancel={onHide} initialTags={initialTags} />
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
+11
-2
@@ -1,11 +1,16 @@
|
||||
import { OLModal } from '@/shared/components/ol/ol-modal'
|
||||
import ModalContentNewProjectForm from './modal-content-new-project-form'
|
||||
import { Tag } from '../../../../../../app/src/Features/Tags/types'
|
||||
|
||||
type ExampleProjectModalProps = {
|
||||
onHide: () => void
|
||||
initialTags?: Tag[]
|
||||
}
|
||||
|
||||
function ExampleProjectModal({ onHide }: ExampleProjectModalProps) {
|
||||
function ExampleProjectModal({
|
||||
onHide,
|
||||
initialTags,
|
||||
}: ExampleProjectModalProps) {
|
||||
return (
|
||||
<OLModal
|
||||
show
|
||||
@@ -14,7 +19,11 @@ function ExampleProjectModal({ onHide }: ExampleProjectModalProps) {
|
||||
id="example-project-modal"
|
||||
backdrop="static"
|
||||
>
|
||||
<ModalContentNewProjectForm onCancel={onHide} template="example" />
|
||||
<ModalContentNewProjectForm
|
||||
onCancel={onHide}
|
||||
template="example"
|
||||
initialTags={initialTags}
|
||||
/>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
+39
-3
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useAsync from '../../../../shared/hooks/use-async'
|
||||
import {
|
||||
@@ -19,6 +19,10 @@ import OLButton from '@/shared/components/ol/ol-button'
|
||||
import OLForm from '@/shared/components/ol/ol-form'
|
||||
import OLFormLabel from '@/shared/components/ol/ol-form-label'
|
||||
import OLFormGroup from '@/shared/components/ol/ol-form-group'
|
||||
import { CloneProjectTag } from '@/features/clone-project-modal/components/clone-project-tag'
|
||||
import { addProjectsToTag } from '@/features/project-list/util/api'
|
||||
import { captureException } from '@/infrastructure/error-reporter'
|
||||
import { Tag } from '../../../../../../app/src/Features/Tags/types'
|
||||
|
||||
type NewProjectData = {
|
||||
project_id: string
|
||||
@@ -34,16 +38,26 @@ type NewProjectData = {
|
||||
type Props = {
|
||||
onCancel: () => void
|
||||
template?: string
|
||||
initialTags?: Tag[]
|
||||
}
|
||||
|
||||
function ModalContentNewProjectForm({ onCancel, template = 'none' }: Props) {
|
||||
function ModalContentNewProjectForm({
|
||||
onCancel,
|
||||
template = 'none',
|
||||
initialTags = [],
|
||||
}: Props) {
|
||||
const { t } = useTranslation()
|
||||
const { autoFocusedRef } = useRefWithAutoFocus<HTMLInputElement>()
|
||||
const [projectName, setProjectName] = useState('')
|
||||
const [projectTags, setProjectTags] = useState<Tag[]>(initialTags)
|
||||
const [redirecting, setRedirecting] = useState(false)
|
||||
const { isLoading, isError, error, runAsync } = useAsync<NewProjectData>()
|
||||
const location = useLocation()
|
||||
|
||||
const removeTag = useCallback((tag: Tag) => {
|
||||
setProjectTags(value => value.filter(item => item._id !== tag._id))
|
||||
}, [])
|
||||
|
||||
const createNewProject = () => {
|
||||
runAsync(
|
||||
postJSON('/project/new', {
|
||||
@@ -53,10 +67,17 @@ function ModalContentNewProjectForm({ onCancel, template = 'none' }: Props) {
|
||||
},
|
||||
})
|
||||
)
|
||||
.then(data => {
|
||||
.then(async data => {
|
||||
if (data.project_id) {
|
||||
// prevents clicking on create again between async load of next page and pending state being finished
|
||||
setRedirecting(true)
|
||||
for (const tag of projectTags) {
|
||||
try {
|
||||
await addProjectsToTag(tag._id, [data.project_id])
|
||||
} catch (err) {
|
||||
captureException(err as Error)
|
||||
}
|
||||
}
|
||||
location.assign(`/project/${data.project_id}`)
|
||||
}
|
||||
})
|
||||
@@ -97,6 +118,21 @@ function ModalContentNewProjectForm({ onCancel, template = 'none' }: Props) {
|
||||
value={projectName}
|
||||
/>
|
||||
</OLFormGroup>
|
||||
|
||||
{projectTags.length > 0 && (
|
||||
<OLFormGroup controlId="new-project-tags-list">
|
||||
<OLFormLabel>{t('tags')}: </OLFormLabel>
|
||||
<div role="listbox" id="new-project-tags-list">
|
||||
{projectTags.map(tag => (
|
||||
<CloneProjectTag
|
||||
key={tag._id}
|
||||
tag={tag}
|
||||
removeTag={removeTag}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</OLFormGroup>
|
||||
)}
|
||||
</OLForm>
|
||||
</OLModalBody>
|
||||
|
||||
|
||||
+9
-3
@@ -5,6 +5,7 @@ import { JSXElementConstructor, lazy, Suspense, useCallback } from 'react'
|
||||
import { Nullable } from '../../../../../../types/utils'
|
||||
import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
|
||||
import { useLocation } from '@/shared/hooks/use-location'
|
||||
import { Tag } from '../../../../../../app/src/Features/Tags/types'
|
||||
|
||||
const UploadProjectModal = lazy(() => import('./upload-project-modal'))
|
||||
const ImportDocumentModal = lazy(() => import('./import-document-modal'))
|
||||
@@ -20,9 +21,14 @@ export type NewProjectButtonModalVariant =
|
||||
type NewProjectButtonModalProps = {
|
||||
modal: Nullable<NewProjectButtonModalVariant>
|
||||
onHide: () => void
|
||||
initialTags?: Tag[]
|
||||
}
|
||||
|
||||
function NewProjectButtonModal({ modal, onHide }: NewProjectButtonModalProps) {
|
||||
function NewProjectButtonModal({
|
||||
modal,
|
||||
onHide,
|
||||
initialTags,
|
||||
}: NewProjectButtonModalProps) {
|
||||
const [importProjectFromGithubModalWrapper] = importOverleafModules(
|
||||
'importProjectFromGithubModalWrapper'
|
||||
)
|
||||
@@ -45,9 +51,9 @@ function NewProjectButtonModal({ modal, onHide }: NewProjectButtonModalProps) {
|
||||
|
||||
switch (modal) {
|
||||
case 'blank_project':
|
||||
return <BlankProjectModal onHide={onHide} />
|
||||
return <BlankProjectModal onHide={onHide} initialTags={initialTags} />
|
||||
case 'example_project':
|
||||
return <ExampleProjectModal onHide={onHide} />
|
||||
return <ExampleProjectModal onHide={onHide} initialTags={initialTags} />
|
||||
case 'upload_project':
|
||||
return (
|
||||
<Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
|
||||
|
||||
@@ -42,6 +42,9 @@ export function ProjectListDsNav() {
|
||||
const isLibraryEnabled = isSplitTestEnabled('overleaf-library')
|
||||
|
||||
const selectedTag = tags.find(tag => tag._id === selectedTagId)
|
||||
const showNewProjectButton =
|
||||
!isLibraryEnabled ||
|
||||
(filter !== 'shared' && filter !== 'archived' && filter !== 'trashed')
|
||||
|
||||
const tableTopArea = (
|
||||
<div className="pt-2 pb-3 d-md-none d-flex gap-2">
|
||||
@@ -54,10 +57,12 @@ export function ProjectListDsNav() {
|
||||
selectedTag={selectedTag}
|
||||
className="overflow-hidden flex-grow-1"
|
||||
/>
|
||||
<NewProjectButton
|
||||
id="new-project-button-projects-table"
|
||||
showAddAffiliationWidget
|
||||
/>
|
||||
{showNewProjectButton && (
|
||||
<NewProjectButton
|
||||
id="new-project-button-projects-table"
|
||||
showAddAffiliationWidget
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -127,7 +132,7 @@ export function ProjectListDsNav() {
|
||||
selectedTag={selectedTag}
|
||||
/>
|
||||
</OLCol>
|
||||
{isLibraryEnabled && (
|
||||
{isLibraryEnabled && showNewProjectButton && (
|
||||
<OLCol className="ms-auto" xs="auto">
|
||||
<NewProjectButton
|
||||
id="new-project-button-projects-table"
|
||||
|
||||
+1
-6
@@ -5,7 +5,6 @@ import {
|
||||
} from '../../context/project-list-context'
|
||||
import TagsList from './tags-list'
|
||||
import ProjectsFilterMenu from '../projects-filter-menu'
|
||||
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
||||
|
||||
type SidebarFilterProps = {
|
||||
filter: Filter
|
||||
@@ -30,13 +29,9 @@ export function SidebarFilter({ filter, text }: SidebarFilterProps) {
|
||||
|
||||
export default function SidebarFilters() {
|
||||
const { t } = useTranslation()
|
||||
const isLibraryEnabled = isSplitTestEnabled('overleaf-library')
|
||||
|
||||
return (
|
||||
<ul className="list-unstyled project-list-filters">
|
||||
{!isLibraryEnabled && (
|
||||
<SidebarFilter filter="all" text={t('all_projects')} />
|
||||
)}
|
||||
<SidebarFilter filter="all" text={t('all_projects')} />
|
||||
<SidebarFilter filter="owned" text={t('your_projects')} />
|
||||
<SidebarFilter filter="shared" text={t('shared_with_you')} />
|
||||
<SidebarFilter filter="archived" text={t('archived_projects')} />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BookBookmark, Folder } from '@phosphor-icons/react'
|
||||
import { useActiveOverallTheme } from '@/shared/hooks/use-active-overall-theme'
|
||||
import getMeta from '@/utils/meta'
|
||||
import overleafLogo from '@/shared/svgs/overleaf-a-ds-solution-mallard.svg'
|
||||
import overleafLogoDark from '@/shared/svgs/overleaf-a-ds-solution-mallard-dark.svg'
|
||||
|
||||
@@ -18,20 +19,23 @@ export function DsNavPageSwitcher({
|
||||
onProjectsClick?: React.MouseEventHandler
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const appName = getMeta('ol-ExposedSettings')?.appName ?? 'Overleaf'
|
||||
const activeOverallTheme = useActiveOverallTheme()
|
||||
|
||||
return (
|
||||
<>
|
||||
{showLogo && (
|
||||
<div className="ds-nav-page-switcher-logo">
|
||||
<img
|
||||
src={
|
||||
activeOverallTheme === 'dark' ? overleafLogoDark : overleafLogo
|
||||
}
|
||||
alt="Overleaf, A Digital Science Solution"
|
||||
height="59"
|
||||
width="130"
|
||||
/>
|
||||
<a href="/" aria-label={appName}>
|
||||
<img
|
||||
src={
|
||||
activeOverallTheme === 'dark' ? overleafLogoDark : overleafLogo
|
||||
}
|
||||
alt="Overleaf, A Digital Science Solution"
|
||||
height="59"
|
||||
width="130"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
<ul
|
||||
|
||||
@@ -112,23 +112,29 @@ body {
|
||||
}
|
||||
|
||||
.project-list-wrapper .project-list-sidebar-wrapper-react {
|
||||
padding-top: calc(var(--spacing-08) - 1px);
|
||||
|
||||
.ds-nav-page-switcher-divider {
|
||||
margin: var(--spacing-05) 0;
|
||||
}
|
||||
|
||||
.project-list-sidebar-scroll {
|
||||
padding-top: 0;
|
||||
padding-right: var(--spacing-05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.library-page {
|
||||
.project-list-wrapper
|
||||
.project-ds-nav-content-and-messages
|
||||
.project-ds-nav-content {
|
||||
@include media-breakpoint-up(md) {
|
||||
border-top-left-radius: 0;
|
||||
border-top: none;
|
||||
ul.project-list-filters {
|
||||
> li > button {
|
||||
padding-left: calc(var(--spacing-05) + 24px + var(--spacing-05));
|
||||
}
|
||||
|
||||
.dropdown-header {
|
||||
padding-left: calc(var(--spacing-06) + 24px + var(--spacing-05));
|
||||
}
|
||||
|
||||
> li.tag button.tag-name {
|
||||
padding-left: calc(var(--spacing-06) + 24px + var(--spacing-05));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
"add_ons_for_any_plan_subheading": "Buy add-ons for any Overleaf plan (including the free plan) to unlock additional features.",
|
||||
"add_or_remove_project_from_tag": "Add or remove project from tag __tagName__",
|
||||
"add_reference": "Add reference",
|
||||
"add_references": "Add references",
|
||||
"add_role_and_department": "Add role and department",
|
||||
"add_to_dictionary": "Add to dictionary",
|
||||
"add_to_tag": "Add to tag",
|
||||
@@ -1519,6 +1520,7 @@
|
||||
"manage_template": "Manage template",
|
||||
"manage_users_subtext": "Add or remove members and assign roles",
|
||||
"manage_your_ai_assist_add_on": "Manage your AI Assist add-on",
|
||||
"manage_your_references_across_projects": "Manage your references across projects.",
|
||||
"managed": "Managed",
|
||||
"managed_user_accounts": "Managed user accounts",
|
||||
"managed_user_accounts_explanation": "Provides tighter management of user access and deletion and allows organizations to keep control of projects when someone leaves the organization",
|
||||
@@ -2477,6 +2479,7 @@
|
||||
"start_typing_find_your_company": " Start typing to find your company",
|
||||
"start_typing_find_your_organization": "Start typing to find your organization",
|
||||
"start_typing_find_your_university": "Start typing to find your university",
|
||||
"start_your_collection_of_most_used_references": "Start your collection of most-used references in the Library, so you can easily add them to any project.",
|
||||
"state": "State",
|
||||
"status_checks": "Status Checks",
|
||||
"still_have_questions": "Still have questions?",
|
||||
|
||||
+40
@@ -4,6 +4,7 @@ import fetchMock from 'fetch-mock'
|
||||
import sinon from 'sinon'
|
||||
import ModalContentNewProjectForm from '../../../../../../frontend/js/features/project-list/components/new-project-button/modal-content-new-project-form'
|
||||
import { location } from '@/shared/components/location'
|
||||
import * as projectListApi from '@/features/project-list/util/api'
|
||||
|
||||
describe('<ModalContentNewProjectForm />', function () {
|
||||
beforeEach(function () {
|
||||
@@ -51,6 +52,45 @@ describe('<ModalContentNewProjectForm />', function () {
|
||||
sinon.assert.calledWith(assignStub, `/project/${projectId}`)
|
||||
})
|
||||
|
||||
it('redirects even when adding tags fails', async function () {
|
||||
const projectId = 'ab123'
|
||||
|
||||
fetchMock.post('/project/new', {
|
||||
status: 200,
|
||||
body: {
|
||||
project_id: projectId,
|
||||
},
|
||||
})
|
||||
|
||||
const addProjectsToTagStub = this.locationWrapperSandbox
|
||||
.stub(projectListApi, 'addProjectsToTag')
|
||||
.rejects(new Error('tag add failed'))
|
||||
|
||||
render(
|
||||
<ModalContentNewProjectForm
|
||||
onCancel={() => {}}
|
||||
initialTags={[{ _id: 'tag-1', user_id: 'user-1', name: 'Tag 1' }]}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Project name'), {
|
||||
target: { value: 'Test Name' },
|
||||
})
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'Create',
|
||||
})
|
||||
)
|
||||
|
||||
const assignStub = this.locationWrapperStub.assign
|
||||
await waitFor(() => {
|
||||
sinon.assert.calledOnce(assignStub)
|
||||
})
|
||||
sinon.assert.calledWith(assignStub, `/project/${projectId}`)
|
||||
sinon.assert.calledWith(addProjectsToTagStub, 'tag-1', [projectId])
|
||||
})
|
||||
|
||||
it('shows error when project name contains "/"', async function () {
|
||||
const errorMessage = 'Project name cannot contain / characters'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user