From dc865b34a92407f01cf07588cb179aa517c382d0 Mon Sep 17 00:00:00 2001 From: "Dusan Mijatovic (PC2020)" Date: Fri, 16 May 2025 12:12:26 +0200 Subject: [PATCH] feat!: upgrade to rsd v3.3.0 as in PR #1479 rsd core commit 8f28f679e88bceb11d5f16d9e7e621d0c1580b6e --- .env.example | 4 +- data-generation/accounts.js | 26 + deployment/.env.example | 8 +- deployment/docker-compose.yml | 36 +- docker-compose.yml | 44 +- frontend/.dockerignore | 3 + frontend/.eslintrc.json | 53 - frontend/.eslintrc.json.license | 7 - frontend/.knip.jsonc | 25 + frontend/.unimportedrc.json | 33 - frontend/.unimportedrc.json.license | 4 - frontend/Dockerfile | 15 +- frontend/Dockerfile.dev | 4 +- frontend/__tests__/CookiesPage.test.tsx | 19 +- frontend/__tests__/OrganisationPage.test.tsx | 37 +- frontend/__tests__/ProjectEditPage.test.tsx | 10 +- frontend/__tests__/ProjectItem.test.tsx | 10 +- .../__tests__/ProjectsOverviewPage.test.tsx | 35 +- frontend/__tests__/SoftwareEditPage.test.tsx | 9 +- frontend/__tests__/SoftwareItem.test.tsx | 85 - frontend/__tests__/SoftwareOverview.test.tsx | 42 +- frontend/__tests__/UserPages.test.tsx | 157 +- .../__tests__/__mocks__/apiContributors.json | 36 +- frontend/auth/ProtectedContent.test.tsx | 12 +- frontend/auth/api/authEndpoint.ts | 4 +- frontend/auth/api/authHelpers.test.ts | 36 +- frontend/auth/api/authHelpers.ts | 11 +- frontend/auth/api/getLoginProviders.tsx | 45 + frontend/auth/api/useLoginProviders.tsx | 38 +- frontend/auth/index.tsx | 24 +- .../permissions/isMaintainerOfCommunity.ts | 8 +- .../isMaintainerOfOrganisation.test.ts | 21 +- .../permissions/isMaintainerOfOrganisation.ts | 74 +- .../permissions/isMaintainerOfProject.test.ts | 8 +- .../isMaintainerOfSoftware.test.ts | 8 +- .../api/fe/auth => auth/providers}/azure.ts | 39 +- .../fe/auth => auth/providers}/helmholtzid.ts | 36 +- .../fe/auth => auth/providers}/linkedin.ts | 37 +- frontend/auth/providers/local.ts | 15 + .../api/fe/auth => auth/providers}/orcid.ts | 39 +- .../fe/auth => auth/providers}/surfconext.ts | 37 +- frontend/auth/refreshSession.test.ts | 6 +- .../components/AppFooter/ContactEmail.tsx | 4 +- frontend/components/AppFooter/index.tsx | 2 +- frontend/components/AppHeader/AddMenu.tsx | 8 +- .../components/AppHeader/AppHeader.test.tsx | 1 - .../components/AppHeader/ResponsiveMenu.tsx | 22 +- frontend/components/AppHeader/index.tsx | 6 +- .../GlobalSearchAutocomplete.test.tsx | 139 + .../GlobalsSearchAutocomplete.test.tsx | 92 - .../NoResultsLabel.tsx | 17 + .../GlobalSearchAutocomplete/RsdHostLabel.tsx | 35 + .../SearchItemIcon.tsx | 26 + .../UnpublishedLabel.tsx | 15 + .../__mocks__/apiGlobalSearch.ts | 29 + .../__mocks__/globalSearchApiResponse.json | 390 +- .../globalSearchApiResponse.json.license | 2 + .../__mocks__/useHasRemotes.tsx | 11 + .../apiGlobalSearch.ts | 57 + .../globalSearchAutocomplete.api.ts | 48 - .../GlobalSearchAutocomplete/index.tsx | 183 +- .../useHasRemotes.tsx | 22 + frontend/components/admin/AdminNav.tsx | 56 +- .../AdminAnnouncementPage.test.tsx | 18 +- .../__mocks__/apiAnnouncement.tsx | 15 +- .../components/admin/categories/index.tsx | 10 +- .../admin/communities/AddCommunityModal.tsx | 2 +- .../admin/communities/CommunityList.tsx | 54 +- .../admin/communities/CommunityListItem.tsx | 109 +- .../admin/communities/NoCommunityAlert.tsx | 4 +- .../communities/RemoveCommunityModal.tsx | 59 + .../admin/communities/apiCommunities.ts | 14 +- .../components/admin/communities/index.tsx | 20 +- .../admin/communities/useAdminCommunities.tsx | 15 +- .../admin/keywords/KeywordEdit.test.tsx | 8 +- .../components/admin/keywords/KeywordEdit.tsx | 6 +- .../admin/keywords/KeywordTable.test.tsx | 8 +- .../admin/logs/__mocks__/apiLogs.ts | 11 +- frontend/components/admin/logs/apiLogs.ts | 5 +- frontend/components/admin/logs/index.tsx | 2 - .../admin/mentions/MentionsOverview.tsx | 10 +- .../admin/mentions/MentionsOverviewList.tsx | 21 +- .../admin/orcid-users/AddOrcidAlert.tsx | 95 - .../ImportOrcidList/OrcidImportReport.tsx | 171 - .../ImportOrcidList/OrcidInputBody.tsx | 120 - .../ImportOrcidList/apiImportOrcidList.tsx | 140 - .../orcid-users/ImportOrcidList/config.ts | 19 - .../orcid-users/ImportOrcidList/index.tsx | 105 - .../admin/orcid-users/OrcidUserItem.tsx | 71 - .../admin/orcid-users/OrcidUserList.tsx | 138 - .../admin/orcid-users/apiOrcidUsers.ts | 289 - .../components/admin/orcid-users/index.tsx | 30 - .../organisations/AddOrganisation.test.tsx | 16 +- .../admin/organisations/AddOrganisation.tsx | 6 +- .../organisations/OrganisationItem.test.tsx | 24 +- .../admin/organisations/OrganisationItem.tsx | 87 +- .../OrganisationsAdminList.test.tsx | 7 +- .../organisations/OrganisationsAdminList.tsx | 64 +- .../organisations/RemoveOrganisationModal.tsx | 59 + .../admin/organisations/apiOrganisation.tsx | 25 +- .../components/admin/organisations/index.tsx | 20 +- .../admin/pages/add/AddPageModal.test.tsx | 30 +- .../admin/pages/add/AddPageModal.tsx | 4 +- .../components/admin/pages/add/addConfig.ts | 4 +- .../pages/edit/EditMarkdownPage.test.tsx | 6 +- .../admin/pages/edit/EditMarkdownPage.tsx | 6 +- .../admin/pages/edit/PageEditIndex.test.tsx | 14 +- .../admin/pages/edit/PageEditorBody.tsx | 4 +- .../admin/pages/edit/SortableNav.tsx | 7 +- .../admin/pages/edit/SortableNavItem.tsx | 44 +- .../admin/pages/saveMarkdownPage.test.ts | 18 +- .../admin/pages/useMarkdownPages.ts | 8 +- .../admin/projects/AdminProjectListItem.tsx | 58 + .../admin/projects/AdminProjectsList.tsx | 68 + .../admin/projects/RemoveProjectModal.tsx | 59 + .../admin/projects/apiAdminProjects.ts | 63 + frontend/components/admin/projects/index.tsx | 22 + .../admin/projects/useAdminProjects.tsx | 86 + .../admin/remote-rsd/NoRemotesAlert.tsx | 16 + .../admin/remote-rsd/RemoteRsdList.tsx | 78 + .../admin/remote-rsd/RemoteRsdListItem.tsx | 109 + .../admin/remote-rsd/RemoteRsdModal.tsx | 243 + .../admin/remote-rsd/RemoveRemoteRsdModal.tsx | 59 + .../admin/remote-rsd/apiRemoteRsd.ts | 211 + .../components/admin/remote-rsd/config.ts | 60 + .../components/admin/remote-rsd/index.tsx | 92 + .../admin/remote-rsd/useRemoteRsd.tsx | 103 + .../admin/rsd-contributors/AvatarOptions.tsx | 181 + .../rsd-contributors/ContributorsTable.tsx | 17 +- .../RsdContributorsPage.test.tsx | 32 +- .../__mocks__/apiRsdContributors.ts | 6 +- .../__mocks__/person_mentions.json | 283 +- .../__mocks__/person_mentions.json.license | 4 +- .../rsd-contributors/apiRsdContributors.ts | 32 +- .../admin/rsd-contributors/config.tsx | 24 +- .../admin/rsd-contributors/index.tsx | 2 + .../rsd-contributors/useContributors.tsx | 8 +- .../components/admin/rsd-info/AddRsdInfo.tsx | 167 + .../admin/rsd-info/NoRsdInfoAlert.tsx | 25 + .../admin/rsd-info/RsdInfoTable.tsx | 78 + .../components/admin/rsd-info/apiRsdInfo.ts | 153 + frontend/components/admin/rsd-info/config.tsx | 113 + frontend/components/admin/rsd-info/index.tsx | 21 + .../components/admin/rsd-info/useRsdInfo.tsx | 98 + .../admin/rsd-invites/CreateRsdInvite.tsx | 91 + .../admin/rsd-invites/apiRsdInvite.ts | 127 + .../components/admin/rsd-invites/index.tsx | 54 + .../admin/rsd-invites/useRsdInvite.tsx | 76 + .../admin/rsd-users/__mocks__/apiRsdUsers.ts | 7 +- .../AddSoftwareHighlights.tsx | 10 +- .../SortableHighlightItem.tsx | 10 +- .../__mocks__/apiSoftwareHighlights.ts | 8 +- .../apiSoftwareHighlights.ts | 5 +- .../admin/software-highlights/index.tsx | 6 +- .../admin/software/AdminSoftwareList.tsx | 69 + .../admin/software/AdminSoftwareListItem.tsx | 58 + .../admin/software/RemoveSoftwareModal.tsx | 59 + .../admin/software/apiAdminSoftware.ts | 63 + frontend/components/admin/software/index.tsx | 22 + .../admin/software/useAdminSoftware.tsx | 86 + .../components/cards/CardContentFrame.tsx | 5 +- frontend/components/cards/CardImageFrame.tsx | 5 +- frontend/components/cards/CardSkeleton.tsx | 25 + .../card => cards}/GridCardSkeleton.tsx | 13 +- .../components/cards/GridListSkeleton.tsx | 38 + frontend/components/cards/KeywordList.tsx | 6 +- frontend/components/cards/RsdHostLabel.tsx | 18 + frontend/components/cards/StatusBanner.tsx | 8 +- .../components/category/CategoriesDialog.tsx | 137 + .../category/CategoriesDialogBody.tsx | 156 + .../category/CategoriesWithHeadlines.tsx | 28 - .../category/CategoryChipFilter.tsx | 109 + .../components/category/CategoryEditForm.tsx | 95 +- .../components/category/CategoryEditTree.tsx | 20 +- .../category/CategoryEditTreeNode.tsx | 37 +- frontend/components/category/CategoryIcon.tsx | 33 + frontend/components/category/CategoryList.tsx | 202 + .../components/category/CategoryStatus.tsx | 26 + .../components/category/CategoryTable.tsx | 91 + frontend/components/category/CategoryTree.tsx | 105 +- frontend/components/category/TreeSelect.tsx | 91 + .../category/__mocks__/apiCategories.ts | 30 + .../components/category/apiCategories.test.ts | 54 + frontend/components/category/apiCategories.ts | 125 +- frontend/components/category/useCategories.ts | 29 +- .../category/useCategoriesFilter.tsx | 92 + .../components/category/useCategoryTree.tsx | 56 + .../category/useReorderedCategories.tsx | 94 + .../d3LineChart/NoDataAvailableChart.tsx | 28 +- .../d3LineChart/SingleLineChart.test.tsx | 4 +- .../charts/d3LineChart/SingleLineChart.tsx | 8 +- .../charts/d3LineChart/drawLineChart.tsx | 36 +- .../charts/d3LineChart/formatData.ts | 5 +- .../d3LineChart/useResizeObserver.test.tsx | 12 +- .../components/communities/CommunityPage.tsx | 88 +- .../components/communities/context/index.tsx | 9 +- .../communities/metadata/CommunityLogo.tsx | 50 +- .../components/communities/metadata/index.tsx | 6 +- .../communities/overview/CommunityCard.tsx | 3 +- .../overview/CommunityListItem.tsx | 11 +- .../communities/overview/CommunityMetrics.tsx | 18 +- .../communities/settings/SettingsNavItems.tsx | 5 +- .../communities/settings/categories/index.tsx | 10 +- .../general/AutosaveCommunityKeywords.tsx | 11 +- .../general/CommunityAdminSection.tsx | 13 +- .../settings/general/apiCommunityKeywords.ts | 2 +- .../general/searchForCommunityKeyword.test.ts | 10 +- .../components/communities/settings/index.tsx | 8 +- .../CommunityMaintainersIndex.test.tsx | 12 +- .../maintainers/apiCommunityMaintainers.ts | 8 +- .../settings/maintainers/index.tsx | 6 +- .../maintainers/useCommunityInvitations.tsx | 13 +- .../software/CommunitySoftwareOverview.tsx | 12 +- .../software/apiCommunitySoftware.ts | 48 +- .../software/card/useSoftwareCardActions.tsx | 2 +- .../filters/OrderCommunitySoftwareBy.tsx | 10 +- .../filters/apiCommunitySoftwareFilters.ts | 77 +- .../communities/software/filters/index.tsx | 33 +- .../filters/useCommunityHasCategories.tsx | 29 + .../components/communities/software/index.tsx | 42 +- .../communities/software/search/index.tsx | 14 +- .../communities/tabs/CommunityTabItems.tsx | 7 +- .../cookies/CookieConsentMatomo.tsx | 6 +- frontend/components/cookies/nodeCookies.ts | 6 +- .../components/cookies/setMatomoPage.test.ts | 7 +- .../feedback/FeedbackPanelButton.test.tsx | 6 +- .../feedback/FeedbackPanelButton.tsx | 40 +- .../components/filter/CategoriesFilter.tsx | 89 + .../components/filter/FilterOption.test.tsx | 15 +- frontend/components/filter/FilterOption.tsx | 12 +- frontend/components/filter/FiltersModal.tsx | 5 +- frontend/components/filter/FiltersPanel.tsx | 6 +- frontend/components/filter/KeywordsFilter.tsx | 18 +- frontend/components/filter/LicensesFilter.tsx | 8 +- .../components/filter/OrganisationsFilter.tsx | 8 +- .../filter/ProgrammingLanguagesFilter.tsx | 8 +- .../filter/ResearchDomainFilter.tsx | 8 +- frontend/components/filter/RsdHostFilter.tsx | 90 + .../filter/useFilterQueryChange.test.tsx | 12 +- .../components/form/AsyncAutocompleteSC.tsx | 1 - .../form/AutosaveControlledMarkdown.test.tsx | 16 +- .../form/AutosaveControlledMarkdown.tsx | 4 +- .../form/ControlledAutocomplete.tsx | 31 +- .../components/form/ControlledImageInput.tsx | 19 +- .../components/form/ControlledSelect.test.tsx | 6 +- frontend/components/form/ControlledSwitch.tsx | 14 +- .../components/form/ControlledTextField.tsx | 22 +- .../components/form/HelperTextWithCounter.tsx | 5 +- frontend/components/form/ImageInput.tsx | 21 + .../form/MarkdownInputWithPreview.tsx | 53 +- frontend/components/form/Searchbox.tsx | 6 +- frontend/components/form/SlugTextField.tsx | 5 +- .../form/SubmitButtonWithListener.tsx | 4 +- .../components/form/TextFieldWithCounter.tsx | 13 +- .../components/home/kin/AboutUsSection.tsx | 6 +- .../components/home/kin/ContributeSection.tsx | 2 +- frontend/components/home/kin/JumboBanner.tsx | 3 +- .../components/home/rsd/AboutUsSection.tsx | 8 +- .../components/home/rsd/GetStartedSection.tsx | 10 +- .../home/rsd/GradientBorderButton.tsx | 8 +- .../components/home/rsd/HomeSectionTitle.tsx | 13 - .../components/home/rsd/HomepageDivider.tsx | 6 +- frontend/components/home/rsd/JumboBanner.tsx | 8 +- .../components/home/rsd/LearnMoreSection.tsx | 6 +- frontend/components/home/rsd/LogoSection.tsx | 8 +- .../home/rsd/OrganisationSignUpButton.tsx | 8 +- .../home/rsd/OrganisationSignUpDialog.tsx | 15 +- .../components/home/rsd/OurGoalsSection.tsx | 14 +- .../components/home/rsd/PersonalSignUp.tsx | 16 +- frontend/components/home/rsd/StatsSection.tsx | 6 +- .../components/home/rsd/TopNewsSection.tsx | 8 +- .../components/icons/SvgFromString.test.tsx | 57 + frontend/components/icons/SvgFromString.tsx | 16 + .../components/keyword/FindKeyword.test.tsx | 13 +- frontend/components/keyword/FindKeyword.tsx | 6 +- .../components/layout/BaseSurfaceRounded.tsx | 19 +- frontend/components/layout/CardTitleMuted.tsx | 20 + .../components/layout/ConfirmDeleteModal.tsx | 27 +- .../components/layout/ContentContainer.tsx | 5 +- .../components/layout/ContentInTheMiddle.tsx | 6 +- frontend/components/layout/ContentLoader.tsx | 4 +- .../components/layout/DarkThemeSection.tsx | 5 +- .../components/layout/FlexibleGridSection.tsx | 10 +- .../components/layout/ImageAsBackground.tsx | 8 +- .../layout/ImageWithPlaceholder.tsx | 12 +- frontend/components/layout/Logo.tsx | 70 + .../LogoMenu.tsx} | 12 +- frontend/components/layout/MarkdownPage.tsx | 18 - frontend/components/layout/MasonryGrid.tsx | 22 - frontend/components/layout/NoContent.tsx | 14 +- .../components/layout/PageErrorMessage.tsx | 18 +- frontend/components/layout/PageTitle.tsx | 6 +- .../layout/ReactMarkdownWithSettings.tsx | 30 +- frontend/components/layout/SidebarPanel.tsx | 12 + frontend/components/layout/SidebarSection.tsx | 21 + frontend/components/layout/SidebarTitle.tsx | 14 + .../components/layout/SortableList.test.tsx | 6 +- frontend/components/layout/SortableList.tsx | 5 +- .../layout/SortableListItemActions.tsx | 26 +- frontend/components/layout/StatCounter.tsx | 29 - frontend/components/layout/StickyHeader.tsx | 6 +- frontend/components/layout/TabAsLink.tsx | 30 + frontend/components/layout/TagChipFilter.tsx | 58 +- frontend/components/layout/UserMenu.test.tsx | 30 +- frontend/components/layout/UserMenu.tsx | 41 +- .../__mocks__/useStickyHeaderBorder.tsx | 3 + .../layout/useStickyHeaderBorder.test.tsx | 12 +- frontend/components/login/LoginButton.tsx | 37 +- frontend/components/login/LoginDialog.tsx | 64 +- frontend/components/login/LoginProviders.tsx | 118 + .../components/maintainers/InvitationList.tsx | 94 +- .../components/maintainers/apiMaintainers.ts | 14 +- .../mention/AccordionForDarkTheme.tsx | 7 +- .../mention/AccordionForLightTheme.tsx | 2 + .../mention/EditMentionModal.test.tsx | 12 +- .../components/mention/EditMentionModal.tsx | 102 +- .../components/mention/FindMention.test.tsx | 23 +- frontend/components/mention/FindMention.tsx | 11 +- .../mention/FindMentionInfoPanel.tsx | 4 +- .../components/mention/FindMentionSection.tsx | 86 +- .../ImportMentionsIndex.test.tsx | 6 +- .../ImportMentions/ImportReportBody.tsx | 2 - .../__mocks__/apiImportMentions.tsx | 9 +- .../ImportMentions/apiImportMentions.tsx | 61 +- .../mention/ImportMentions/index.tsx | 2 +- .../mention/MentionEditFeatured.test.tsx | 12 +- .../mention/MentionEditFeatured.tsx | 6 +- .../mention/MentionEditItem.test.tsx | 12 +- .../components/mention/MentionEditSection.tsx | 64 +- .../mention/MentionItemFeatured.tsx | 5 +- .../components/mention/MentionsSection.tsx | 6 +- frontend/components/mention/config.ts | 30 +- .../components/mention/editMentionContext.tsx | 6 +- .../mention/useEditMentionReducer.test.tsx | 48 +- .../menu/IconBtnMenuOnAction.test.tsx | 4 +- .../components/menu/IconBtnMenuOnAction.tsx | 6 +- frontend/components/news/add/AddNewsCard.tsx | 80 +- frontend/components/news/apiNews.tsx | 6 +- .../news/edit/AutosaveNewsImage.tsx | 13 +- .../news/edit/EditNewsStickyHeader.tsx | 2 +- frontend/components/news/edit/index.tsx | 6 +- frontend/components/news/item/NavButtons.tsx | 2 +- frontend/components/news/item/NewsItemNav.tsx | 5 +- .../news/overview/list/ListItemNews.tsx | 6 +- .../organisation/apiOrganisations.ts | 131 +- .../context/OrganisationContext.tsx | 17 +- .../context/UserSettingsContext.tsx | 52 - .../organisation/metadata/Links.tsx | 7 +- .../metadata/OrganisationLogo.test.tsx | 30 +- .../metadata/OrganisationLogo.tsx | 62 +- .../organisation/metadata/RorLocation.tsx | 6 +- .../organisation/metadata/RorType.tsx | 13 +- .../organisation/metadata/index.tsx | 47 +- .../overview/card/CountryLabel.tsx | 6 +- .../card/KinOrganisationCardMetrics.tsx | 20 + .../overview/card/OrganisationCard.tsx | 15 +- .../overview/card/OrganisationCardMetrics.tsx | 6 +- .../OrganisationProjectsIndex.test.tsx | 26 +- .../projects/OrganisationProjectsOverview.tsx | 23 +- .../projects/card/useProjectCardActions.tsx | 14 +- .../projects/filters/OrgOrderProjectsBy.tsx | 13 +- .../__mocks__/useOrgProjectCategoriesList.tsx | 12 + .../organisation/projects/filters/index.tsx | 29 +- .../filters/useOrgProjectCategoriesList.tsx | 117 + .../filters/useOrgProjectDomainsList.tsx | 32 +- .../filters/useOrgProjectKeywordsList.tsx | 41 +- .../useOrgProjectOrganisationsList.tsx | 27 +- .../filters/useOrgProjectStatusList.tsx | 32 +- .../organisation/projects/index.tsx | 29 +- .../search/OrgSearchProjectSection.tsx | 9 +- .../projects/useAdminMenuOptions.tsx | 8 +- .../projects/useOrganisationProjects.tsx | 18 +- .../projects/useProjectParams.tsx | 22 +- .../projects/useQueryChange.test.tsx | 16 +- .../OrganisationReleasesIndex.test.tsx | 23 +- .../organisation/releases/ReleaseList.tsx | 20 +- .../releases/ReleaseNavButton.tsx | 20 +- .../__mocks__/apiOrganisationReleases.ts | 7 +- .../__mocks__/releases_by_organisation.json | 38 +- .../releases_by_organisation.json.license | 3 +- .../organisation/releases/index.tsx | 51 +- .../organisation/releases/useReleaseCount.tsx | 47 - .../releases/useSoftwareReleases.tsx | 8 +- .../OrganisationSettingsIndex.test.tsx | 85 +- .../settings/SettingsNavItems.tsx | 12 +- .../settings/SettingsPageContent.tsx | 9 +- .../OrganisationAboutPageIndex.test.tsx | 8 +- .../settings/categories/index.tsx | 51 + .../settings/general/RsdAdminSection.tsx | 6 +- .../settings/general/generalSettingsConfig.ts | 11 +- .../organisation/settings/general/index.tsx | 13 +- .../organisation/settings/index.tsx | 8 +- .../OrganisationMaintainerLinks.tsx | 6 +- .../OrganisationMaintainersIndex.test.tsx | 10 +- .../apiOrganisationMaintainers.test.ts | 8 +- .../settings/maintainers/index.tsx | 6 +- .../useOrganisationInvitations.tsx | 11 +- .../updateOrganisationSettings.test.ts | 8 +- .../OrganisationSoftwareIndex.test.tsx | 25 +- .../software/OrganisationSoftwareOverview.tsx | 25 +- .../software/card/useSoftwareCardActions.tsx | 8 +- .../software/filters/OrgOrderSoftwareBy.tsx | 13 +- .../useOrgSoftwareCategoriesList.tsx | 11 + .../organisation/software/filters/index.tsx | 30 +- .../filters/useOrgSoftwareCategoriesList.tsx | 115 + .../filters/useOrgSoftwareKeywordsList.tsx | 32 +- .../filters/useOrgSoftwareLanguagesList.tsx | 22 +- .../filters/useOrgSoftwareLicensesList.tsx | 24 +- .../software/filters/useSoftwareParams.tsx | 16 +- .../organisation/software/index.tsx | 31 +- .../software/list/AdminSoftwareListItem.tsx | 10 +- .../patchSoftwareForOrganisation.test.ts | 8 +- .../search/OrgSearchSoftwareSection.tsx | 9 +- .../software/useOrganisationSoftware.tsx | 16 +- .../tabs/OrganisationTabItems.tsx | 16 +- .../organisation/tabs/OrganisationTabs.tsx | 11 +- .../organisation/tabs/TabContent.tsx | 26 +- .../organisation/tabs/useSelectedTab.tsx | 19 +- .../units/OrganisationUnitsIndex.test.tsx | 64 +- .../organisation/units/ResearchUnitList.tsx | 8 +- .../organisation/units/ResearchUnitModal.tsx | 67 +- .../units/__mocks__/mockUnits.json | 2 +- .../components/organisation/units/index.tsx | 74 +- .../units/useOrganisationUnits.ts | 64 - .../AggregatedPersonModal.tsx} | 196 +- .../person/AggregatedPersonOption.tsx | 8 +- frontend/components/person/AvatarOptions.tsx | 115 +- .../components/person/AvatarOptionsPerson.tsx | 87 +- .../FindPerson.tsx} | 115 +- .../person/__mocks__/useAggregatedPerson.tsx | 28 + .../person/__mocks__/useRoleOptions.tsx | 11 + frontend/components/person/config.ts | 80 + frontend/components/person/findRSDPerson.ts | 92 + frontend/components/person/groupByOrcid.ts | 22 +- frontend/components/person/searchForPerson.ts | 109 +- .../components/person/useAggregatedPerson.tsx | 64 + frontend/components/person/useRoleOptions.tsx | 56 + .../components/profile/ProfileSearchPanel.tsx | 9 +- frontend/components/profile/apiProfile.ts | 107 +- .../components/profile/metadata/index.tsx | 92 +- .../components/profile/projects/index.tsx | 23 +- .../components/profile/software/index.tsx | 23 +- .../profile/tabs/ProfileTabItems.tsx | 8 +- frontend/components/profile/tabs/index.tsx | 40 +- .../components/projects/ProjectCategories.tsx | 42 + .../components/projects/ProjectFunding.tsx | 85 +- frontend/components/projects/ProjectInfo.tsx | 21 +- .../components/projects/ProjectKeywords.tsx | 13 +- frontend/components/projects/ProjectLinks.tsx | 26 +- .../components/projects/ProjectSidebar.tsx | 29 +- .../components/projects/ProjectStatus.tsx | 12 +- .../RelatedProjectStatus.tsx} | 10 +- .../RelatedProjectsCard.tsx} | 24 +- .../RelatedProjectsGrid.tsx} | 10 +- .../projects/RelatedProjectsSection.tsx | 12 +- .../components/projects/ResearchDomains.tsx | 16 +- .../projects/add/AddProjectCard.test.tsx | 7 +- .../projects/add/AddProjectCard.tsx | 93 +- .../projects/edit/EditProjectStickyHeader.tsx | 6 +- .../projects/edit/editProjectContext.tsx | 6 +- .../projects/edit/editProjectPages.tsx | 19 +- .../AutosaveFundingOrganisations.test.tsx | 31 +- .../AutosaveFundingOrganisations.tsx | 4 +- .../information/AutosaveProjectImage.test.tsx | 55 +- .../edit/information/AutosaveProjectImage.tsx | 15 +- .../AutosaveProjectKeywords.test.tsx | 37 +- .../information/AutosaveProjectKeywords.tsx | 4 +- .../information/AutosaveProjectLinks.test.tsx | 14 +- .../information/AutosaveProjectPeriod.tsx | 4 +- .../AutosaveProjectTextField.test.tsx | 6 +- .../AutosaveResearchDomains.test.tsx | 14 +- .../information/FindFundingOrganisation.tsx | 6 +- .../information/ProjectInformationForm.tsx | 7 +- .../information/__mocks__/searchForKeyword.ts | 9 +- .../__mocks__/useProjectToEdit.tsx | 3 + .../projects/edit/information/config.ts | 7 +- .../edit/information/patchProjectInfo.test.ts | 10 +- .../edit/information/patchProjectInfo.ts | 5 +- .../edit/information/searchForKeyword.test.ts | 8 +- .../edit/information/searchForKeyword.ts | 8 +- .../information/useProjectToEdit.test.tsx | 4 +- .../EditProjectMaintainersIndex.test.tsx | 2 + .../maintainers/ProjectMaintainerLinks.tsx | 6 +- .../projects/edit/maintainers/index.tsx | 6 +- .../maintainers/useProjectInvitations.tsx | 11 +- .../mentions/citations/NoCitationItems.tsx | 18 - .../edit/mentions/getProjectMention.tsx | 2 +- .../mentions/impact/EditImpactProvider.tsx | 6 +- .../impact/EditProjectImpactTab.test.tsx | 36 +- .../impact/impactForProjectApi.test.ts | 12 +- .../mentions/impact/impactForProjectApi.ts | 5 +- .../projects/edit/mentions/impact/index.tsx | 6 +- .../mentions/output/EditOutputProvider.tsx | 4 +- .../output/EditProjectOutputTab.test.tsx | 35 +- .../projects/edit/mentions/output/index.tsx | 6 +- .../output/outputForProjectApi.test.ts | 11 +- .../mentions/output/outputForProjectApi.ts | 5 +- .../EditProjectOrganisationsIndex.test.tsx | 121 +- .../organisations/ProjectCategoriesDialog.tsx | 60 + .../__mocks__/apiProjectOrganisations.ts | 18 + .../organisations/apiProjectOrganisations.ts | 48 + .../projects/edit/organisations/config.ts | 6 +- .../projects/edit/organisations/index.tsx | 103 +- .../useParticipatingOrganisations.ts | 42 +- .../organisations/useProjectCategories.tsx | 133 + .../EditRelatedProjectsIndex.test.tsx | 23 +- .../related-projects/FindRelatedProject.tsx | 8 +- .../related-projects/RelatedProjectList.tsx | 8 +- .../projects/edit/related-projects/index.tsx | 10 +- .../EditRelatedSoftwareIndex.test.tsx | 20 +- .../related-software/FindRelatedSoftware.tsx | 8 +- .../related-software/RelatedSoftwareList.tsx | 8 +- .../projects/edit/related-software/index.tsx | 10 +- .../edit/team/AggregatedMemberModal.test.tsx | 97 - .../edit/team/AggregatedMemberModal.tsx | 263 - .../edit/team/EditProjectTeamIndex.test.tsx | 157 +- .../edit/team/EditTeamMemberModal.tsx | 383 - .../projects/edit/team/FindMember.tsx | 176 - .../edit/team/SortableTeamMemberList.tsx | 13 +- .../edit/team/__mocks__/teamMembers.json | 2 + .../team/__mocks__/teamMembers.json.license | 2 + ...Members.test.ts => apiTeamMembers.test.ts} | 21 +- .../{editTeamMembers.ts => apiTeamMembers.ts} | 2 + .../components/projects/edit/team/config.ts | 74 +- .../components/projects/edit/team/index.tsx | 257 +- .../projects/edit/team/useTeamMembers.tsx | 233 +- .../EditProjectTestimonialsIndex.test.tsx | 271 + .../__mocks__/project_testimonials.json | 16 + .../project_testimonials.json.license | 4 + .../testimonials/apiProjectTestimonial.ts | 152 + .../projects/edit/testimonials/config.ts | 27 + .../projects/edit/testimonials/index.tsx | 191 + .../testimonials/useProjectTestimonial.tsx | 148 + .../overview/cards/ProjectOverviewGrid.tsx | 5 +- .../projects/overview/cards/ProjectPeriod.tsx | 6 +- .../overview/cards/ResearchDomainTitle.tsx | 19 +- .../overview/filters/projectFiltersApi.ts | 39 +- .../list/ListImageWithGradientPlaceholder.tsx | 6 +- .../overview/list/ProjectListItemContent.tsx | 5 +- .../overview/list/ProjectOverviewList.tsx | 5 +- .../overview/search/ProjectSearchSection.tsx | 9 +- .../overview/search/ViewToggleGroup.tsx | 8 +- .../overview/useProjectOverviewParams.test.ts | 16 +- .../overview/useProjectOverviewParams.ts | 2 +- frontend/components/search/SearchInput.tsx | 24 +- .../search/useSearchParams.test.tsx | 24 +- .../components/seo/getOrganisationSitemap.ts | 8 +- frontend/components/seo/getProjectsSitemap.ts | 10 +- frontend/components/seo/getSoftwareSitemap.ts | 4 +- .../components/snackbar/MuiSnackbar.test.tsx | 3 + .../snackbar/MuiSnackbarContext.tsx | 6 +- .../snackbar/MuiSnackbarProvider.test.tsx | 18 +- frontend/components/snackbar/useSnackbar.tsx | 6 +- .../components/software/AboutLanguages.tsx | 16 +- frontend/components/software/AboutLicense.tsx | 10 +- .../software/AboutPackageManagers.tsx | 29 +- frontend/components/software/AboutSection.tsx | 43 +- .../components/software/AboutSourceCode.tsx | 31 +- .../components/software/AboutStatement.tsx | 4 +- .../components/software/CategoriesSection.tsx | 49 +- .../components/software/CategoriesSidebar.tsx | 49 + .../components/software/CitationSection.tsx | 7 +- .../components/software/CommitsChart.test.tsx | 105 +- frontend/components/software/CommitsChart.tsx | 59 +- .../software/CommunitiesSection.tsx | 8 +- .../components/software/ContactPersonCard.tsx | 12 +- .../components/software/ContributorsList.tsx | 14 +- .../software/ContributorsSection.tsx | 20 +- .../components/software/GetStartedSection.tsx | 23 +- .../software/OrganisationsSection.tsx | 11 +- frontend/components/software/PersonalInfo.tsx | 27 +- .../RelatedSoftwareCard.tsx} | 10 +- .../RelatedSoftwareGrid.tsx} | 19 +- .../software/RelatedSoftwareSection.tsx | 12 +- .../components/software/SoftwareKeywords.tsx | 10 +- .../components/software/TestimonialItem.tsx | 5 +- .../software/TestimonialsSection.tsx | 4 +- .../software/add/AddSoftwareCard.test.tsx | 5 +- .../software/add/AddSoftwareCard.tsx | 102 +- .../software/edit/EditSoftwareNav.tsx | 29 +- .../edit/EditSoftwareStickyHeader.tsx | 6 +- .../CommunityAddCategoriesDialog.tsx | 243 + .../communities/CommunityCategoriesDialog.tsx | 70 + .../edit/communities/FindCommunity.tsx | 40 +- .../communities/SoftwareCommunityList.tsx | 14 +- .../communities/SoftwareCommunityListItem.tsx | 57 +- .../communities/apiSoftwareCommunities.ts | 16 + .../software/edit/communities/index.tsx | 120 +- .../communities/useCommunityCategories.tsx | 120 + .../communities/useSoftwareCommunities.tsx | 12 +- .../contributors/EditContributorModal.tsx | 383 - .../EditSoftwareContributorsIndex.test.tsx | 196 +- .../contributors/GetContributorsFromDoi.tsx | 25 +- .../__mocks__/softwareContributors.json | 36 +- .../softwareContributors.json.license | 2 + .../edit/contributors/apiContributors.ts} | 25 +- .../software/edit/contributors/index.tsx | 285 +- .../contributors/useSoftwareContributors.tsx | 224 +- .../software/edit/editSoftwareConfig.tsx | 114 +- .../software/edit/editSoftwareContext.tsx | 9 +- .../software/edit/editSoftwarePages.tsx | 7 +- .../information/AutosaveRemoteMarkdown.tsx | 104 +- .../information/AutosaveSoftwareLogo.test.tsx | 44 +- .../edit/information/AutosaveSoftwareLogo.tsx | 25 +- .../AutosaveSoftwareMarkdown.test.tsx | 41 +- .../information/AutosaveSoftwareMarkdown.tsx | 30 +- .../AutosaveSoftwareTextField.test.tsx | 10 +- .../EditSoftwareDescriptionInputs.tsx | 6 +- .../information/patchSoftwareTable.test.ts | 10 +- .../edit/information/patchSoftwareTable.ts | 5 +- .../edit/links/AutosaveRepositoryPlatform.tsx | 5 +- .../edit/links/AutosaveRepositoryUrl.test.tsx | 9 +- .../edit/links/AutosaveRepositoryUrl.tsx | 12 +- .../edit/links/AutosaveSoftwareCategories.tsx | 242 +- .../edit/links/AutosaveSoftwareKeywords.tsx | 8 +- .../links/AutosaveSoftwareLicenses.test.tsx | 43 +- .../edit/links/AutosaveSoftwareLicenses.tsx | 14 +- .../software/edit/links/EditLicenseModal.tsx | 5 +- .../edit/links/EditSoftwareMetadataForm.tsx | 1 + .../edit/links/EditSoftwareMetadataInputs.tsx | 14 +- .../software/edit/links/SoftwareLinksInfo.tsx | 11 +- .../edit/links/ValidateConceptDoi.test.tsx | 11 +- .../__mocks__/searchForSoftwareKeyword.ts | 3 +- .../components/software/edit/links/config.tsx | 15 +- .../links/searchForSoftwareKeyword.test.ts | 10 +- .../edit/links/useSoftwareToEdit.test.tsx | 15 +- .../software/edit/links/useSoftwareToEdit.tsx | 6 +- .../EditSoftwareMaintainersIndex.test.tsx | 2 + .../software/edit/maintainers/index.tsx | 6 +- .../maintainers/useSoftwareInvitations.tsx | 11 +- .../EditSoftwareCitationsTab.test.tsx | 1 + .../citations/apiCitationsBySoftware.tsx | 2 +- .../edit/mentions/getSoftwareMention.tsx | 2 +- .../output/EditRelatedOutputProvider.tsx | 2 +- .../output/EditSoftwareOutputTab.test.tsx | 32 +- .../mentions/output/apiRelatedOutput.test.ts | 13 +- .../edit/mentions/output/apiRelatedOutput.ts | 5 +- .../software/edit/mentions/output/index.tsx | 6 +- .../EditReferencePapersTab.test.tsx | 29 +- .../reference-papers/ReferencePapersInfo.tsx | 11 +- .../edit/mentions/reference-papers/index.tsx | 6 +- .../software/edit/mentions/utils.ts | 15 +- .../organisations/EditOrganisationModal.tsx | 6 +- .../EditSoftwareOrganisationsIndex.test.tsx | 133 +- .../edit/organisations/FindOrganisation.tsx | 6 +- .../SoftwareCategoriesDialog.tsx | 64 + .../SortableOrganisationItem.tsx | 18 +- .../SortableOrganisationsList.tsx | 8 +- .../__mocks__/apiSoftwareOrganisations.ts | 40 + .../organisations/apiSoftwareOrganisations.ts | 73 + .../software/edit/organisations/index.tsx | 117 +- .../organisationForSoftware.test.ts | 10 +- .../organisations/organisationForSoftware.ts | 8 +- .../useParticipatingOrganisations.ts | 58 +- .../organisations/useSoftwareCategories.tsx | 127 + .../EditPackageManagerModal.tsx | 132 +- .../package-managers/PackageManagerItem.tsx | 40 +- .../package-managers/PackageManagersInfo.tsx | 2 +- .../__mocks__/apiPackageManager.ts | 91 +- .../package-managers/apiPackageManager.ts | 140 +- .../software/edit/package-managers/config.ts | 42 +- .../software/edit/package-managers/index.tsx | 43 +- .../package-managers/usePackageManagers.tsx | 27 +- .../software/edit/related-projects/index.tsx | 10 +- .../software/edit/related-software/index.tsx | 12 +- .../edit/services/PackageManagerServices.tsx | 3 + .../edit/services/ServiceInfoListItem.tsx | 72 +- .../edit/services/SoftwareRepoServices.tsx | 21 +- .../edit/services/apiSoftwareServices.tsx | 62 +- .../software/edit/services/config.ts | 19 +- .../software/edit/services/index.tsx | 6 +- .../EditSoftwareTestimonialsIndex.test.tsx | 22 +- .../testimonials/EditTestimonialModal.tsx | 12 +- .../testimonials/apiSoftwareTestimonial.ts} | 41 +- .../software/edit/testimonials/index.tsx | 175 +- .../testimonials/useSoftwareTestimonials.tsx | 150 + .../edit/testimonials/useTestimonials.tsx | 46 - .../overview/SoftwareOverviewContent.tsx | 58 +- .../overview/cards/ExternalLinkIcon.tsx | 26 + .../overview/cards/SoftwareCardContent.tsx | 16 +- .../overview/cards/SoftwareGridCard.tsx | 27 +- .../overview/cards/SoftwareMasonryCard.tsx | 36 +- .../overview/cards/SoftwareOverviewGrid.tsx | 6 +- .../cards/SoftwareOverviewMasonry.tsx | 5 +- .../overview/filters/SoftwareFiltersModal.tsx | 36 +- .../software/overview/filters/index.tsx | 69 +- .../overview/filters/softwareFiltersApi.ts | 138 +- .../overview/filters/useHasCategories.tsx | 25 + .../overview/highlights/HighlightsCard.tsx | 10 +- .../highlights/HighlightsCarousel.tsx | 6 +- .../overview/list/OverviewListItem.tsx | 7 +- .../software/overview/list/RsdHostBanner.tsx | 33 + .../overview/list/SoftwareListItemContent.tsx | 22 +- .../overview/list/SoftwareOverviewList.tsx | 5 +- .../software/overview/search/SelectRows.tsx | 12 +- .../overview/search/SoftwareSearchSection.tsx | 9 +- .../overview/search/ViewToggleGroup.tsx | 14 +- .../overview/useSoftwareOverviewParams.ts | 4 +- .../overview/useSoftwareOverviewProps.tsx | 74 + .../software/useContributorList.tsx | 10 +- frontend/components/table/EditableCell.tsx | 68 +- frontend/components/table/EditableTable.tsx | 14 +- frontend/components/table/TableBody.tsx | 31 +- frontend/components/table/TableHeader.tsx | 51 +- frontend/components/typography/Icon.tsx | 39 - .../components/typography/SidebarHeadline.tsx | 17 - frontend/components/user/UserNav.tsx | 69 - frontend/components/user/UserNavItems.tsx | 99 - frontend/components/user/UserSection.tsx | 58 - frontend/components/user/UserTitle.tsx | 35 - .../communities/UserCommunitiesIndex.test.tsx | 73 - .../communities/UserCommunitiesOverview.tsx | 51 + .../components/user/communities/index.tsx | 82 +- .../communities/useUserCommunities.test.tsx | 9 +- .../user/communities/useUserCommunities.tsx | 32 +- .../components/user/context/UserContext.tsx | 73 + frontend/components/user/getUserCount.test.ts | 8 +- frontend/components/user/getUserCounts.ts | 12 +- .../components/user/metadata/UserAvatar.tsx | 124 + .../components/user/metadata/UserInfo.tsx | 38 + frontend/components/user/metadata/index.tsx | 22 + .../organisations/OrganisationListItem.tsx | 6 +- .../UserOrganisationsIndex.test.tsx | 79 - .../UserOrganisationsOverview.tsx | 55 + .../components/user/organisations/index.tsx | 90 +- .../useUserOrganisations.test.tsx | 9 +- .../organisations/useUserOrganisations.tsx | 43 +- .../user/project-quality/FullScreenTable.tsx | 4 +- .../user/project-quality/TableWrapper.tsx | 4 +- .../components/user/project-quality/index.tsx | 36 +- .../user/project/UserProjectIndex.test.tsx | 73 - .../user/project/UserProjectsOverview.tsx | 79 + frontend/components/user/project/index.tsx | 110 +- .../user/project/useUserProjects.test.tsx | 9 +- .../user/project/useUserProjects.tsx | 64 +- .../components/user/search/ItemsPerPage.tsx | 53 + .../components/user/search/SearchPanel.tsx | 44 + .../components/user/settings/BasicProfile.tsx | 50 - .../user/settings/LoginForAccountList.tsx | 125 - .../user/settings/UserAgreementsSection.tsx | 94 - .../user/settings/UserSettingsContent.tsx | 27 + .../settings/__mocks__/useLoginForAccount.tsx | 13 - .../user/settings/about/AutosaveAboutMe.tsx | 50 + .../components/user/settings/about/config.ts | 17 + .../components/user/settings/about/index.tsx | 52 + .../{ => agreements}/RemoveAccount.tsx | 10 +- .../UserAgreementModal.test.tsx | 22 +- .../{ => agreements}/UserAgreementModal.tsx | 6 +- .../__mocks__/useUserAgreements.tsx | 2 + .../user/settings/agreements/index.tsx | 73 + .../{ => agreements}/useUserAgreements.tsx | 30 +- .../user/settings/apiLinkLinkedInProps.ts | 50 + .../user/settings/apiLinkOrcidProps.ts | 53 +- frontend/components/user/settings/index.tsx | 94 +- .../settings/nav/UserSettingsNavItems.tsx | 42 + .../components/user/settings/nav/index.tsx | 55 + .../user/settings/profile/AccountInfo.tsx | 23 + .../profile/AuthenticationMethods.tsx | 73 + .../profile/AutosaveProfileSwitch.tsx | 23 + .../profile/AutosaveProfileTextField.tsx | 30 + .../LinkAccountBtn.tsx} | 23 +- .../user/settings/profile/LinkAccounts.tsx | 52 + .../user/settings/profile/LoginItem.tsx | 63 + .../user/settings/profile/ProfileInput.tsx | 105 + .../settings/profile/__mocks__/logins.json | 26 + .../profile/__mocks__/logins.json.license | 4 + .../profile/__mocks__/useLoginForUser.tsx | 37 + .../settings/profile/apiLoginForAccount.tsx | 76 + .../user/settings/profile/apiUserProfile.ts | 207 + .../user/settings/profile/index.tsx | 48 + .../user/settings/profile/useLoginForUser.tsx | 36 + .../settings/profile/usePatchUserProfile.tsx | 55 + .../user/settings/useLoginForAccount.tsx | 135 - .../user/software/UserSoftwareIndex.test.tsx | 74 - .../user/software/UserSoftwareOverview.tsx | 74 + .../software/__mocks__/useUserSoftware.tsx | 3 - frontend/components/user/software/index.tsx | 104 +- .../user/software/useUserSoftware.test.tsx | 9 +- .../user/software/useUserSoftware.tsx | 49 +- .../components/user/tabs/UserTabContent.tsx | 60 + .../components/user/tabs/UserTabItems.tsx | 96 + frontend/components/user/tabs/UserTabs.tsx | 77 + frontend/config/RsdPluginContext.tsx | 64 + frontend/config/UserSettingsContext.tsx | 25 +- frontend/config/defaultSettings.json | 2 +- frontend/config/defaultSettings.json.license | 1 + frontend/config/getPlugins.ts | 72 + frontend/config/getSettingsServerSide.ts | 5 +- frontend/config/menuItems.tsx | 22 +- frontend/config/rsdSettingsReducer.ts | 10 +- frontend/config/useModules.ts | 24 + frontend/config/useUserMenuItems.tsx | 28 +- frontend/eslint.config.mjs | 67 + frontend/jest.config.js | 11 +- frontend/jest.setup.js | 21 +- frontend/next-env.d.ts | 2 +- frontend/{next.config.js => next.config.ts} | 37 +- frontend/{next.headers.js => next.headers.ts} | 9 +- frontend/next.rewrites.js | 55 - frontend/next.rewrites.ts | 61 + frontend/package-lock.json | 10125 +++++++++------- frontend/package.json | 98 +- frontend/pages/_app.tsx | 64 +- frontend/pages/_document.tsx | 2 - frontend/pages/{news/add.tsx => add/news.tsx} | 0 .../{projects/add.tsx => add/project.tsx} | 15 +- .../{software/add.tsx => add/software.tsx} | 12 +- frontend/pages/admin/categories.tsx | 10 +- frontend/pages/admin/communities.tsx | 8 +- .../admin/{orcid-users.tsx => projects.tsx} | 23 +- frontend/pages/admin/public-pages.tsx | 4 +- frontend/pages/admin/remote-rsd.tsx | 68 + frontend/pages/admin/rsd-info.tsx | 75 + frontend/pages/admin/rsd-invites.tsx | 27 + frontend/pages/admin/software-highlights.tsx | 12 +- frontend/pages/admin/software.tsx | 47 + frontend/pages/api/fe/auth/index.ts | 133 +- frontend/pages/api/fe/auth/local.ts | 15 - frontend/pages/api/fe/markdown/raw.ts | 124 + frontend/pages/api/fe/mention/crossref.ts | 28 + .../mention/{impact.ts => find_by_title.ts} | 110 +- frontend/pages/api/fe/mention/output.ts | 137 - frontend/pages/api/fe/mention/software.ts | 134 - frontend/pages/api/fe/token/refresh.ts | 12 +- frontend/pages/communities/[slug]/about.tsx | 21 +- .../pages/communities/[slug]/rejected.tsx | 49 +- .../pages/communities/[slug]/requests.tsx | 49 +- .../pages/communities/[slug]/settings.tsx | 16 +- .../pages/communities/[slug]/software.tsx | 37 +- frontend/pages/communities/index.tsx | 24 +- frontend/pages/cookies.tsx | 78 +- frontend/pages/index.tsx | 5 +- frontend/pages/invite/rsd/[id].tsx | 92 + frontend/pages/login/failed.tsx | 25 +- frontend/pages/login/index.tsx | 84 + frontend/pages/news/[date]/[slug]/edit.tsx | 2 +- frontend/pages/news/[date]/[slug]/index.tsx | 4 +- frontend/pages/news/index.tsx | 15 +- frontend/pages/organisations/[...slug].tsx | 149 +- frontend/pages/organisations/index.tsx | 10 +- frontend/pages/page/[slug].tsx | 35 +- .../pages/profile/{[orcid] => [id]}/[tab].tsx | 117 +- .../pages/projects/[slug]/edit/[page].tsx | 8 +- frontend/pages/projects/[slug]/index.tsx | 71 +- frontend/pages/projects/index.tsx | 60 +- .../pages/software/[slug]/edit/[page].tsx | 8 +- frontend/pages/software/[slug]/index.tsx | 90 +- frontend/pages/software/index.tsx | 173 +- frontend/pages/spotlights/index.tsx | 42 +- frontend/pages/user/[section].tsx | 157 +- frontend/postcss.config.js | 5 +- frontend/public/data/settings.json | 2 + frontend/styles/custom.css | 189 - frontend/styles/global.css | 188 +- frontend/styles/rsdMuiTheme.ts | 10 +- frontend/types/AutocompleteOptions.ts | 9 +- frontend/types/Category.ts | 31 +- frontend/types/Contributor.ts | 47 +- frontend/types/Datacite.ts | 105 +- frontend/types/Invitation.ts | 11 - frontend/types/Mention.ts | 24 +- frontend/types/Organisation.ts | 59 +- frontend/types/Project.ts | 19 +- frontend/types/SoftwareTypes.ts | 39 +- frontend/types/SpdxLicense.ts | 3 + frontend/types/Testimonial.ts | 4 +- frontend/types/TreeNode.test.ts | 49 + frontend/types/TreeNode.ts | 109 +- frontend/utils/__mocks__/editSoftware.ts | 28 +- frontend/utils/__mocks__/getSoftware.ts | 39 +- frontend/utils/categories.ts | 128 - frontend/utils/contentSecurityPolicy.test.ts | 17 +- frontend/utils/contentSecurityPolicy.ts | 13 +- frontend/utils/copyToClipboard.test.ts | 8 +- frontend/utils/dateFn.ts | 27 +- frontend/utils/editImage.ts | 28 + frontend/utils/editMentions.ts | 40 +- frontend/utils/editOrganisation.ts | 21 +- frontend/utils/editProject.ts | 14 +- frontend/utils/editSoftware.ts | 2 +- frontend/utils/extractQueryParam.test.ts | 9 +- frontend/utils/extractQueryParam.ts | 87 +- frontend/utils/fetchHelpers.test.ts | 71 +- frontend/utils/fetchHelpers.ts | 143 +- frontend/utils/findRSDPerson.ts | 56 - frontend/utils/getBrowser.tsx | 8 +- frontend/utils/getCrossref.ts | 55 +- frontend/utils/getDOI.ts | 90 +- frontend/utils/getDataCite.ts | 4 +- frontend/utils/getDisplayName.ts | 4 +- frontend/utils/getInfoFromDatacite.test.ts | 13 +- frontend/utils/getInfoFromDatacite.ts | 9 +- frontend/utils/getORCID.ts | 11 +- frontend/utils/getOpenalex.ts | 82 +- frontend/utils/getProjects.ts | 27 +- frontend/utils/getPropsFromObject.ts | 4 +- frontend/utils/getROR.test.ts | 66 +- frontend/utils/getROR.ts | 41 +- frontend/utils/getSoftware.ts | 98 +- frontend/utils/getUnusedInvitations.ts | 18 - frontend/utils/handleFileUpload.ts | 23 +- frontend/utils/itemsNotInReferenceList.ts | 54 +- frontend/utils/jest/WithAppContext.tsx | 20 +- frontend/utils/jest/mockFetch.ts | 3 + frontend/utils/jest/utils.js | 12 + frontend/utils/logger.test.js | 10 +- frontend/utils/maxText.ts | 4 +- frontend/utils/postgrestUrl.test.ts | 18 +- frontend/utils/postgrestUrl.ts | 161 +- frontend/utils/promisePool.test.ts | 25 + frontend/utils/promisePool.ts | 41 + frontend/utils/useOnUnsavedChange.tsx | 9 +- frontend/utils/useValidateImageSrc.ts | 34 +- frontend/utils/userSettings.ts | 52 +- 914 files changed, 28306 insertions(+), 17808 deletions(-) delete mode 100644 frontend/.eslintrc.json delete mode 100644 frontend/.eslintrc.json.license create mode 100644 frontend/.knip.jsonc delete mode 100644 frontend/.unimportedrc.json delete mode 100644 frontend/.unimportedrc.json.license delete mode 100644 frontend/__tests__/SoftwareItem.test.tsx create mode 100644 frontend/auth/api/getLoginProviders.tsx rename frontend/{pages/api/fe/auth => auth/providers}/azure.ts (70%) rename frontend/{pages/api/fe/auth => auth/providers}/helmholtzid.ts (74%) rename frontend/{pages/api/fe/auth => auth/providers}/linkedin.ts (87%) create mode 100644 frontend/auth/providers/local.ts rename frontend/{pages/api/fe/auth => auth/providers}/orcid.ts (69%) rename frontend/{pages/api/fe/auth => auth/providers}/surfconext.ts (72%) create mode 100644 frontend/components/GlobalSearchAutocomplete/GlobalSearchAutocomplete.test.tsx delete mode 100644 frontend/components/GlobalSearchAutocomplete/GlobalsSearchAutocomplete.test.tsx create mode 100644 frontend/components/GlobalSearchAutocomplete/NoResultsLabel.tsx create mode 100644 frontend/components/GlobalSearchAutocomplete/RsdHostLabel.tsx create mode 100644 frontend/components/GlobalSearchAutocomplete/SearchItemIcon.tsx create mode 100644 frontend/components/GlobalSearchAutocomplete/UnpublishedLabel.tsx create mode 100644 frontend/components/GlobalSearchAutocomplete/__mocks__/apiGlobalSearch.ts create mode 100644 frontend/components/GlobalSearchAutocomplete/__mocks__/useHasRemotes.tsx create mode 100644 frontend/components/GlobalSearchAutocomplete/apiGlobalSearch.ts delete mode 100644 frontend/components/GlobalSearchAutocomplete/globalSearchAutocomplete.api.ts create mode 100644 frontend/components/GlobalSearchAutocomplete/useHasRemotes.tsx create mode 100644 frontend/components/admin/communities/RemoveCommunityModal.tsx delete mode 100644 frontend/components/admin/orcid-users/AddOrcidAlert.tsx delete mode 100644 frontend/components/admin/orcid-users/ImportOrcidList/OrcidImportReport.tsx delete mode 100644 frontend/components/admin/orcid-users/ImportOrcidList/OrcidInputBody.tsx delete mode 100644 frontend/components/admin/orcid-users/ImportOrcidList/apiImportOrcidList.tsx delete mode 100644 frontend/components/admin/orcid-users/ImportOrcidList/config.ts delete mode 100644 frontend/components/admin/orcid-users/ImportOrcidList/index.tsx delete mode 100644 frontend/components/admin/orcid-users/OrcidUserItem.tsx delete mode 100644 frontend/components/admin/orcid-users/OrcidUserList.tsx delete mode 100644 frontend/components/admin/orcid-users/apiOrcidUsers.ts delete mode 100644 frontend/components/admin/orcid-users/index.tsx create mode 100644 frontend/components/admin/organisations/RemoveOrganisationModal.tsx create mode 100644 frontend/components/admin/projects/AdminProjectListItem.tsx create mode 100644 frontend/components/admin/projects/AdminProjectsList.tsx create mode 100644 frontend/components/admin/projects/RemoveProjectModal.tsx create mode 100644 frontend/components/admin/projects/apiAdminProjects.ts create mode 100644 frontend/components/admin/projects/index.tsx create mode 100644 frontend/components/admin/projects/useAdminProjects.tsx create mode 100644 frontend/components/admin/remote-rsd/NoRemotesAlert.tsx create mode 100644 frontend/components/admin/remote-rsd/RemoteRsdList.tsx create mode 100644 frontend/components/admin/remote-rsd/RemoteRsdListItem.tsx create mode 100644 frontend/components/admin/remote-rsd/RemoteRsdModal.tsx create mode 100644 frontend/components/admin/remote-rsd/RemoveRemoteRsdModal.tsx create mode 100644 frontend/components/admin/remote-rsd/apiRemoteRsd.ts create mode 100644 frontend/components/admin/remote-rsd/config.ts create mode 100644 frontend/components/admin/remote-rsd/index.tsx create mode 100644 frontend/components/admin/remote-rsd/useRemoteRsd.tsx create mode 100644 frontend/components/admin/rsd-contributors/AvatarOptions.tsx create mode 100644 frontend/components/admin/rsd-info/AddRsdInfo.tsx create mode 100644 frontend/components/admin/rsd-info/NoRsdInfoAlert.tsx create mode 100644 frontend/components/admin/rsd-info/RsdInfoTable.tsx create mode 100644 frontend/components/admin/rsd-info/apiRsdInfo.ts create mode 100644 frontend/components/admin/rsd-info/config.tsx create mode 100644 frontend/components/admin/rsd-info/index.tsx create mode 100644 frontend/components/admin/rsd-info/useRsdInfo.tsx create mode 100644 frontend/components/admin/rsd-invites/CreateRsdInvite.tsx create mode 100644 frontend/components/admin/rsd-invites/apiRsdInvite.ts create mode 100644 frontend/components/admin/rsd-invites/index.tsx create mode 100644 frontend/components/admin/rsd-invites/useRsdInvite.tsx create mode 100644 frontend/components/admin/software/AdminSoftwareList.tsx create mode 100644 frontend/components/admin/software/AdminSoftwareListItem.tsx create mode 100644 frontend/components/admin/software/RemoveSoftwareModal.tsx create mode 100644 frontend/components/admin/software/apiAdminSoftware.ts create mode 100644 frontend/components/admin/software/index.tsx create mode 100644 frontend/components/admin/software/useAdminSoftware.tsx create mode 100644 frontend/components/cards/CardSkeleton.tsx rename frontend/components/{organisation/software/card => cards}/GridCardSkeleton.tsx (63%) create mode 100644 frontend/components/cards/GridListSkeleton.tsx create mode 100644 frontend/components/cards/RsdHostLabel.tsx create mode 100644 frontend/components/category/CategoriesDialog.tsx create mode 100644 frontend/components/category/CategoriesDialogBody.tsx delete mode 100644 frontend/components/category/CategoriesWithHeadlines.tsx create mode 100644 frontend/components/category/CategoryChipFilter.tsx create mode 100644 frontend/components/category/CategoryIcon.tsx create mode 100644 frontend/components/category/CategoryList.tsx create mode 100644 frontend/components/category/CategoryStatus.tsx create mode 100644 frontend/components/category/CategoryTable.tsx create mode 100644 frontend/components/category/TreeSelect.tsx create mode 100644 frontend/components/category/__mocks__/apiCategories.ts create mode 100644 frontend/components/category/apiCategories.test.ts create mode 100644 frontend/components/category/useCategoriesFilter.tsx create mode 100644 frontend/components/category/useCategoryTree.tsx create mode 100644 frontend/components/category/useReorderedCategories.tsx create mode 100644 frontend/components/communities/software/filters/useCommunityHasCategories.tsx create mode 100644 frontend/components/filter/CategoriesFilter.tsx create mode 100644 frontend/components/filter/RsdHostFilter.tsx create mode 100644 frontend/components/form/ImageInput.tsx delete mode 100644 frontend/components/home/rsd/HomeSectionTitle.tsx create mode 100644 frontend/components/icons/SvgFromString.test.tsx create mode 100644 frontend/components/icons/SvgFromString.tsx create mode 100644 frontend/components/layout/CardTitleMuted.tsx create mode 100644 frontend/components/layout/Logo.tsx rename frontend/components/{organisation/metadata/OrganisationLogoMenu.tsx => layout/LogoMenu.tsx} (90%) delete mode 100644 frontend/components/layout/MarkdownPage.tsx delete mode 100644 frontend/components/layout/MasonryGrid.tsx create mode 100644 frontend/components/layout/SidebarPanel.tsx create mode 100644 frontend/components/layout/SidebarSection.tsx create mode 100644 frontend/components/layout/SidebarTitle.tsx delete mode 100644 frontend/components/layout/StatCounter.tsx create mode 100644 frontend/components/layout/TabAsLink.tsx create mode 100644 frontend/components/login/LoginProviders.tsx delete mode 100644 frontend/components/organisation/context/UserSettingsContext.tsx create mode 100644 frontend/components/organisation/overview/card/KinOrganisationCardMetrics.tsx create mode 100644 frontend/components/organisation/projects/filters/__mocks__/useOrgProjectCategoriesList.tsx create mode 100644 frontend/components/organisation/projects/filters/useOrgProjectCategoriesList.tsx delete mode 100644 frontend/components/organisation/releases/useReleaseCount.tsx create mode 100644 frontend/components/organisation/settings/categories/index.tsx create mode 100644 frontend/components/organisation/software/filters/__mocks__/useOrgSoftwareCategoriesList.tsx create mode 100644 frontend/components/organisation/software/filters/useOrgSoftwareCategoriesList.tsx delete mode 100644 frontend/components/organisation/units/useOrganisationUnits.ts rename frontend/components/{software/edit/contributors/AggregatedContributorModal.tsx => person/AggregatedPersonModal.tsx} (56%) rename frontend/components/{software/edit/contributors/FindContributor.tsx => person/FindPerson.tsx} (62%) create mode 100644 frontend/components/person/__mocks__/useAggregatedPerson.tsx create mode 100644 frontend/components/person/__mocks__/useRoleOptions.tsx create mode 100644 frontend/components/person/config.ts create mode 100644 frontend/components/person/findRSDPerson.ts create mode 100644 frontend/components/person/useAggregatedPerson.tsx create mode 100644 frontend/components/person/useRoleOptions.tsx create mode 100644 frontend/components/projects/ProjectCategories.tsx rename frontend/components/{user/project/ProjectStatus.tsx => projects/RelatedProjectStatus.tsx} (60%) rename frontend/components/{user/project/ProjectCard.tsx => projects/RelatedProjectsCard.tsx} (82%) rename frontend/components/{user/project/ProjectsGrid.tsx => projects/RelatedProjectsGrid.tsx} (65%) delete mode 100644 frontend/components/projects/edit/mentions/citations/NoCitationItems.tsx create mode 100644 frontend/components/projects/edit/organisations/ProjectCategoriesDialog.tsx create mode 100644 frontend/components/projects/edit/organisations/__mocks__/apiProjectOrganisations.ts create mode 100644 frontend/components/projects/edit/organisations/apiProjectOrganisations.ts create mode 100644 frontend/components/projects/edit/organisations/useProjectCategories.tsx delete mode 100644 frontend/components/projects/edit/team/AggregatedMemberModal.test.tsx delete mode 100644 frontend/components/projects/edit/team/AggregatedMemberModal.tsx delete mode 100644 frontend/components/projects/edit/team/EditTeamMemberModal.tsx delete mode 100644 frontend/components/projects/edit/team/FindMember.tsx rename frontend/components/projects/edit/team/{editTeamMembers.test.ts => apiTeamMembers.test.ts} (77%) rename frontend/components/projects/edit/team/{editTeamMembers.ts => apiTeamMembers.ts} (95%) create mode 100644 frontend/components/projects/edit/testimonials/EditProjectTestimonialsIndex.test.tsx create mode 100644 frontend/components/projects/edit/testimonials/__mocks__/project_testimonials.json create mode 100644 frontend/components/projects/edit/testimonials/__mocks__/project_testimonials.json.license create mode 100644 frontend/components/projects/edit/testimonials/apiProjectTestimonial.ts create mode 100644 frontend/components/projects/edit/testimonials/config.ts create mode 100644 frontend/components/projects/edit/testimonials/index.tsx create mode 100644 frontend/components/projects/edit/testimonials/useProjectTestimonial.tsx create mode 100644 frontend/components/software/CategoriesSidebar.tsx rename frontend/components/{user/software/SoftwareCard.tsx => software/RelatedSoftwareCard.tsx} (92%) rename frontend/components/{user/software/SoftwareGrid.tsx => software/RelatedSoftwareGrid.tsx} (72%) create mode 100644 frontend/components/software/edit/communities/CommunityAddCategoriesDialog.tsx create mode 100644 frontend/components/software/edit/communities/CommunityCategoriesDialog.tsx create mode 100644 frontend/components/software/edit/communities/useCommunityCategories.tsx delete mode 100644 frontend/components/software/edit/contributors/EditContributorModal.tsx rename frontend/{utils/editContributors.ts => components/software/edit/contributors/apiContributors.ts} (83%) create mode 100644 frontend/components/software/edit/organisations/SoftwareCategoriesDialog.tsx create mode 100644 frontend/components/software/edit/organisations/__mocks__/apiSoftwareOrganisations.ts create mode 100644 frontend/components/software/edit/organisations/apiSoftwareOrganisations.ts create mode 100644 frontend/components/software/edit/organisations/useSoftwareCategories.tsx rename frontend/{utils/editTestimonial.ts => components/software/edit/testimonials/apiSoftwareTestimonial.ts} (72%) create mode 100644 frontend/components/software/edit/testimonials/useSoftwareTestimonials.tsx delete mode 100644 frontend/components/software/edit/testimonials/useTestimonials.tsx create mode 100644 frontend/components/software/overview/cards/ExternalLinkIcon.tsx create mode 100644 frontend/components/software/overview/filters/useHasCategories.tsx create mode 100644 frontend/components/software/overview/list/RsdHostBanner.tsx create mode 100644 frontend/components/software/overview/useSoftwareOverviewProps.tsx delete mode 100644 frontend/components/typography/Icon.tsx delete mode 100644 frontend/components/typography/SidebarHeadline.tsx delete mode 100644 frontend/components/user/UserNav.tsx delete mode 100644 frontend/components/user/UserNavItems.tsx delete mode 100644 frontend/components/user/UserSection.tsx delete mode 100644 frontend/components/user/UserTitle.tsx delete mode 100644 frontend/components/user/communities/UserCommunitiesIndex.test.tsx create mode 100644 frontend/components/user/communities/UserCommunitiesOverview.tsx create mode 100644 frontend/components/user/context/UserContext.tsx create mode 100644 frontend/components/user/metadata/UserAvatar.tsx create mode 100644 frontend/components/user/metadata/UserInfo.tsx create mode 100644 frontend/components/user/metadata/index.tsx delete mode 100644 frontend/components/user/organisations/UserOrganisationsIndex.test.tsx create mode 100644 frontend/components/user/organisations/UserOrganisationsOverview.tsx delete mode 100644 frontend/components/user/project/UserProjectIndex.test.tsx create mode 100644 frontend/components/user/project/UserProjectsOverview.tsx create mode 100644 frontend/components/user/search/ItemsPerPage.tsx create mode 100644 frontend/components/user/search/SearchPanel.tsx delete mode 100644 frontend/components/user/settings/BasicProfile.tsx delete mode 100644 frontend/components/user/settings/LoginForAccountList.tsx delete mode 100644 frontend/components/user/settings/UserAgreementsSection.tsx create mode 100644 frontend/components/user/settings/UserSettingsContent.tsx delete mode 100644 frontend/components/user/settings/__mocks__/useLoginForAccount.tsx create mode 100644 frontend/components/user/settings/about/AutosaveAboutMe.tsx create mode 100644 frontend/components/user/settings/about/config.ts create mode 100644 frontend/components/user/settings/about/index.tsx rename frontend/components/user/settings/{ => agreements}/RemoveAccount.tsx (93%) rename frontend/components/user/settings/{ => agreements}/UserAgreementModal.test.tsx (81%) rename frontend/components/user/settings/{ => agreements}/UserAgreementModal.tsx (98%) rename frontend/components/user/settings/{ => agreements}/__mocks__/useUserAgreements.tsx (80%) create mode 100644 frontend/components/user/settings/agreements/index.tsx rename frontend/components/user/settings/{ => agreements}/useUserAgreements.tsx (84%) create mode 100644 frontend/components/user/settings/apiLinkLinkedInProps.ts create mode 100644 frontend/components/user/settings/nav/UserSettingsNavItems.tsx create mode 100644 frontend/components/user/settings/nav/index.tsx create mode 100644 frontend/components/user/settings/profile/AccountInfo.tsx create mode 100644 frontend/components/user/settings/profile/AuthenticationMethods.tsx create mode 100644 frontend/components/user/settings/profile/AutosaveProfileSwitch.tsx create mode 100644 frontend/components/user/settings/profile/AutosaveProfileTextField.tsx rename frontend/components/user/settings/{LinkOrcidButton.tsx => profile/LinkAccountBtn.tsx} (51%) create mode 100644 frontend/components/user/settings/profile/LinkAccounts.tsx create mode 100644 frontend/components/user/settings/profile/LoginItem.tsx create mode 100644 frontend/components/user/settings/profile/ProfileInput.tsx create mode 100644 frontend/components/user/settings/profile/__mocks__/logins.json create mode 100644 frontend/components/user/settings/profile/__mocks__/logins.json.license create mode 100644 frontend/components/user/settings/profile/__mocks__/useLoginForUser.tsx create mode 100644 frontend/components/user/settings/profile/apiLoginForAccount.tsx create mode 100644 frontend/components/user/settings/profile/apiUserProfile.ts create mode 100644 frontend/components/user/settings/profile/index.tsx create mode 100644 frontend/components/user/settings/profile/useLoginForUser.tsx create mode 100644 frontend/components/user/settings/profile/usePatchUserProfile.tsx delete mode 100644 frontend/components/user/settings/useLoginForAccount.tsx delete mode 100644 frontend/components/user/software/UserSoftwareIndex.test.tsx create mode 100644 frontend/components/user/software/UserSoftwareOverview.tsx create mode 100644 frontend/components/user/tabs/UserTabContent.tsx create mode 100644 frontend/components/user/tabs/UserTabItems.tsx create mode 100644 frontend/components/user/tabs/UserTabs.tsx create mode 100644 frontend/config/RsdPluginContext.tsx create mode 100644 frontend/config/getPlugins.ts create mode 100644 frontend/config/useModules.ts create mode 100644 frontend/eslint.config.mjs rename frontend/{next.config.js => next.config.ts} (64%) rename frontend/{next.headers.js => next.headers.ts} (94%) delete mode 100644 frontend/next.rewrites.js create mode 100644 frontend/next.rewrites.ts rename frontend/pages/{news/add.tsx => add/news.tsx} (100%) rename frontend/pages/{projects/add.tsx => add/project.tsx} (69%) rename frontend/pages/{software/add.tsx => add/software.tsx} (69%) rename frontend/pages/admin/{orcid-users.tsx => projects.tsx} (62%) create mode 100644 frontend/pages/admin/remote-rsd.tsx create mode 100644 frontend/pages/admin/rsd-info.tsx create mode 100644 frontend/pages/admin/rsd-invites.tsx create mode 100644 frontend/pages/admin/software.tsx delete mode 100644 frontend/pages/api/fe/auth/local.ts create mode 100644 frontend/pages/api/fe/markdown/raw.ts create mode 100644 frontend/pages/api/fe/mention/crossref.ts rename frontend/pages/api/fe/mention/{impact.ts => find_by_title.ts} (54%) delete mode 100644 frontend/pages/api/fe/mention/output.ts delete mode 100644 frontend/pages/api/fe/mention/software.ts create mode 100644 frontend/pages/invite/rsd/[id].tsx create mode 100644 frontend/pages/login/index.tsx rename frontend/pages/profile/{[orcid] => [id]}/[tab].tsx (57%) delete mode 100644 frontend/styles/custom.css delete mode 100644 frontend/types/Invitation.ts create mode 100644 frontend/types/TreeNode.test.ts delete mode 100644 frontend/utils/categories.ts delete mode 100644 frontend/utils/findRSDPerson.ts delete mode 100644 frontend/utils/getUnusedInvitations.ts create mode 100644 frontend/utils/jest/utils.js create mode 100644 frontend/utils/promisePool.test.ts create mode 100644 frontend/utils/promisePool.ts diff --git a/.env.example b/.env.example index ecf25e0..3b2116f 100644 --- a/.env.example +++ b/.env.example @@ -60,13 +60,13 @@ RSD_ENVIRONMENT=prod # Allowed values are: SURFCONEXT, ORCID, AZURE, LINKEDIN or LOCAL # if env value is not provided default provider is set to be SURFCONEXT # if you add the value "LOCAL", then local accounts are enabled, USE THIS FOR TESTING PURPOSES ONLY -RSD_AUTH_PROVIDERS=LINKEDIN;ORCID;LOCAL +RSD_AUTH_PROVIDERS=LINKEDIN:EVERYONE;ORCID:INVITE_ONLY # consumed by services: authentication, frontend (api/fe) # provide a list of supported OpenID auth providers for coupling with the user's RSD account # the values should be separated by semicolon (;) # Allowed values are: ORCID -RSD_AUTH_COUPLE_PROVIDERS=ORCID +RSD_AUTH_COUPLE_PROVIDERS=ORCID;LINKEDIN # Define a semicolon-separated list of user email addresses which are allowed to # login to the RSD. If the variable is left empty, or is not defined, all users diff --git a/data-generation/accounts.js b/data-generation/accounts.js index 640a3ff..c9d12d4 100644 --- a/data-generation/accounts.js +++ b/data-generation/accounts.js @@ -6,6 +6,7 @@ export async function generateAccounts(orcids){ const accounts = await postAccountsToBackend(100); const ids = accounts.map(a => a.id) const logins = await postToBackend('/login_for_account', generateLoginForAccount(ids, orcids)) + const profiles = await postToBackend('/user_profile', generateUserProfiles(ids)) // console.log('accounts, login_for_accounts done'); return ids } @@ -80,3 +81,28 @@ export function generateLoginForAccount(accountIds, orcids) { return login_for_accounts; } +function generateUserProfiles(accountIds) { + const user_profiles = accountIds.map(account => { + let given_names = faker.person.firstName(); + let family_names = faker.person.lastName(); + // user_profile table props + return { + account, + given_names, + family_names, + email_address: faker.internet.email({ + firstName: given_names, + lastName: family_names, + }), + role: faker.person.jobTitle(), + affiliation: faker.company.name(), + is_public: + faker.helpers.maybe(() => true, { + probability: 0.5, + }) ?? false, + avatar_id: null, + description: null, + }; + }); + return user_profiles; +} \ No newline at end of file diff --git a/deployment/.env.example b/deployment/.env.example index eea82c2..0345568 100644 --- a/deployment/.env.example +++ b/deployment/.env.example @@ -60,13 +60,13 @@ RSD_ENVIRONMENT=prod # Allowed values are: SURFCONEXT, ORCID, AZURE, LINKEDIN or LOCAL # if env value is not provided default provider is set to be SURFCONEXT # if you add the value "LOCAL", then local accounts are enabled, USE THIS FOR TESTING PURPOSES ONLY -RSD_AUTH_PROVIDERS=SURFCONEXT;LOCAL +RSD_AUTH_PROVIDERS=LINKEDIN:EVERYONE;ORCID:INVITE_ONLY # consumed by services: authentication, frontend (api/fe) # provide a list of supported OpenID auth providers for coupling with the user's RSD account # the values should be separated by semicolon (;) # Allowed values are: ORCID -# RSD_AUTH_COUPLE_PROVIDERS=ORCID +# RSD_AUTH_COUPLE_PROVIDERS=ORCID;LINKEDIN # Define a semicolon-separated list of user email addresses which are allowed to # login to the RSD. If the variable is left empty, or is not defined, all users @@ -198,8 +198,8 @@ CROSSREF_CONTACT_EMAIL= # consumed by: frontend # URL (should end with a trailing slash) and ID for Matomo Tracking Code -# MATOMO_URL= -# MATOMO_ID= +MATOMO_URL= +MATOMO_ID= # consumed by: scrapers # LIBRARIES_IO_ACCESS_TOKEN= diff --git a/deployment/docker-compose.yml b/deployment/docker-compose.yml index f20f862..dc5cf42 100644 --- a/deployment/docker-compose.yml +++ b/deployment/docker-compose.yml @@ -5,7 +5,7 @@ services: database: container_name: database - image: ghcr.io/research-software-directory/rsd-saas/database:v2.29.0 + image: ghcr.io/research-software-directory/rsd-saas/database:v3.3.0 expose: - 5432 environment: @@ -25,7 +25,7 @@ services: backend: container_name: backend - image: ghcr.io/research-software-directory/rsd-saas/backend:v2.29.0 + image: ghcr.io/research-software-directory/rsd-saas/backend:v3.3.0 expose: - 3500 environment: @@ -43,7 +43,7 @@ services: auth: container_name: auth - image: ghcr.io/research-software-directory/rsd-saas/auth:v2.29.0 + image: ghcr.io/research-software-directory/rsd-saas/auth:v3.3.0 expose: - 7000 environment: @@ -52,10 +52,13 @@ services: - POSTGREST_URL - RSD_AUTH_COUPLE_PROVIDERS - RSD_AUTH_PROVIDERS - - RSD_AUTH_USER_MAIL_WHITELIST - SURFCONEXT_CLIENT_ID - SURFCONEXT_REDIRECT - SURFCONEXT_WELL_KNOWN_URL + - HELMHOLTZID_CLIENT_ID + - HELMHOLTZID_REDIRECT + - HELMHOLTZID_WELL_KNOWN_URL + - HELMHOLTZID_SCOPES - ORCID_CLIENT_ID - ORCID_REDIRECT - ORCID_REDIRECT_COUPLE @@ -66,7 +69,9 @@ services: - AZURE_ORGANISATION - LINKEDIN_CLIENT_ID - LINKEDIN_REDIRECT + - LINKEDIN_REDIRECT_COUPLE - LINKEDIN_WELL_KNOWN_URL + - HELMHOLTZID_ALLOW_EXTERNAL_USERS - AUTH_SURFCONEXT_CLIENT_SECRET - AUTH_HELMHOLTZID_CLIENT_SECRET - AUTH_ORCID_CLIENT_SECRET @@ -102,16 +107,31 @@ services: - net restart: unless-stopped + background-services: + image: ghcr.io/research-software-directory/rsd-saas/background-services:v3.3.0 + environment: + # it uses values from .env file + - POSTGRES_DB_HOST + - POSTGRES_DB_HOST_PORT + - POSTGRES_DB + - POSTGRES_USER + - POSTGRES_PASSWORD + depends_on: + - database + networks: + - net + frontend: container_name: frontend image: ghcr.io/research-software-directory/kin-rpd/frontend:latest environment: - # it uses values from .env file + # it uses values from .env file - POSTGREST_URL - PGRST_JWT_SECRET - RSD_AUTH_URL - RSD_AUTH_PROVIDERS - RSD_AUTH_COUPLE_PROVIDERS + - RSD_REVERSE_PROXY_URL - MATOMO_URL - MATOMO_ID - SURFCONEXT_CLIENT_ID @@ -119,6 +139,11 @@ services: - SURFCONEXT_WELL_KNOWN_URL - SURFCONEXT_SCOPES - SURFCONEXT_RESPONSE_MODE + - HELMHOLTZID_CLIENT_ID + - HELMHOLTZID_REDIRECT + - HELMHOLTZID_WELL_KNOWN_URL + - HELMHOLTZID_SCOPES + - HELMHOLTZID_RESPONSE_MODE - ORCID_CLIENT_ID - ORCID_REDIRECT - ORCID_REDIRECT_COUPLE @@ -133,6 +158,7 @@ services: - AZURE_DESCRIPTION_HTML - LINKEDIN_CLIENT_ID - LINKEDIN_REDIRECT + - LINKEDIN_REDIRECT_COUPLE - LINKEDIN_WELL_KNOWN_URL - CROSSREF_CONTACT_EMAIL expose: diff --git a/docker-compose.yml b/docker-compose.yml index 0db8c69..80c226a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: database: - image: ghcr.io/research-software-directory/rsd-saas/database:v3.1.0 + image: ghcr.io/research-software-directory/rsd-saas/database:v3.3.0 ports: # enable connection from outside (development mode) - "5432:5432" @@ -23,7 +23,7 @@ services: - net backend: - image: ghcr.io/research-software-directory/rsd-saas/backend:v3.1.0 + image: ghcr.io/research-software-directory/rsd-saas/backend:v3.3.0 expose: - 3500 environment: @@ -39,7 +39,7 @@ services: - net auth: - image: ghcr.io/research-software-directory/rsd-saas/auth:v3.1.0 + image: ghcr.io/research-software-directory/rsd-saas/auth:v3.3.0 ports: - 5005:5005 expose: @@ -50,10 +50,13 @@ services: - POSTGREST_URL - RSD_AUTH_COUPLE_PROVIDERS - RSD_AUTH_PROVIDERS - - RSD_AUTH_USER_MAIL_WHITELIST - SURFCONEXT_CLIENT_ID - SURFCONEXT_REDIRECT - SURFCONEXT_WELL_KNOWN_URL + - HELMHOLTZID_CLIENT_ID + - HELMHOLTZID_REDIRECT + - HELMHOLTZID_WELL_KNOWN_URL + - HELMHOLTZID_SCOPES - ORCID_CLIENT_ID - ORCID_REDIRECT - ORCID_REDIRECT_COUPLE @@ -64,7 +67,9 @@ services: - AZURE_ORGANISATION - LINKEDIN_CLIENT_ID - LINKEDIN_REDIRECT + - LINKEDIN_REDIRECT_COUPLE - LINKEDIN_WELL_KNOWN_URL + - HELMHOLTZID_ALLOW_EXTERNAL_USERS - AUTH_SURFCONEXT_CLIENT_SECRET - AUTH_HELMHOLTZID_CLIENT_SECRET - AUTH_ORCID_CLIENT_SECRET @@ -85,7 +90,7 @@ services: ] scrapers: - image: ghcr.io/research-software-directory/rsd-saas/scrapers:v3.1.0 + image: ghcr.io/research-software-directory/rsd-saas/scrapers:v3.3.0 environment: # it uses values from .env file - POSTGREST_URL @@ -104,13 +109,27 @@ services: networks: - net + background-services: + image: ghcr.io/research-software-directory/rsd-saas/background-services:v3.3.0 + environment: + # it uses values from .env file + - POSTGRES_DB_HOST + - POSTGRES_DB_HOST_PORT + - POSTGRES_DB + - POSTGRES_USER + - POSTGRES_PASSWORD + depends_on: + - database + networks: + - net + frontend: build: context: ./frontend # dockerfile to use for build dockerfile: Dockerfile # update version number to correspond to frontend/package.json - image: kin-rpd/frontend:0.1.0 + image: kin-rpd/frontend:1.0.0 environment: # it uses values from .env file - POSTGREST_URL @@ -118,6 +137,7 @@ services: - RSD_AUTH_URL - RSD_AUTH_PROVIDERS - RSD_AUTH_COUPLE_PROVIDERS + - RSD_REVERSE_PROXY_URL - MATOMO_URL - MATOMO_ID - SURFCONEXT_CLIENT_ID @@ -125,6 +145,11 @@ services: - SURFCONEXT_WELL_KNOWN_URL - SURFCONEXT_SCOPES - SURFCONEXT_RESPONSE_MODE + - HELMHOLTZID_CLIENT_ID + - HELMHOLTZID_REDIRECT + - HELMHOLTZID_WELL_KNOWN_URL + - HELMHOLTZID_SCOPES + - HELMHOLTZID_RESPONSE_MODE - ORCID_CLIENT_ID - ORCID_REDIRECT - ORCID_REDIRECT_COUPLE @@ -139,6 +164,7 @@ services: - AZURE_DESCRIPTION_HTML - LINKEDIN_CLIENT_ID - LINKEDIN_REDIRECT + - LINKEDIN_REDIRECT_COUPLE - LINKEDIN_WELL_KNOWN_URL - CROSSREF_CONTACT_EMAIL expose: @@ -161,7 +187,7 @@ services: context: ./documentation # dockerfile to use for build dockerfile: Dockerfile - image: kin-rpd/documentation:0.1.0 + image: kin-rpd/documentation:1.0.0 expose: - "80" networks: @@ -170,7 +196,7 @@ services: nginx: build: context: ./nginx - image: kin-rpd/nginx:0.0.1 + image: kin-rpd/nginx:1.0.0 ports: - "80:80" - "443:443" @@ -191,7 +217,7 @@ services: #---------------------------------------------- data-generation: build: ./data-generation - image: kin-rpd/generation:0.0.1 + image: kin-rpd/generation:1.0.0 environment: # it needs to be here to use values from .env file - PGRST_JWT_SECRET diff --git a/frontend/.dockerignore b/frontend/.dockerignore index 8fd5ace..0344c7c 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -1,5 +1,7 @@ # SPDX-FileCopyrightText: 2021 Dusan Mijatovic (dv4all) # SPDX-FileCopyrightText: 2021 dv4all +# SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center) +# SPDX-FileCopyrightText: 2024 Netherlands eScience Center # # SPDX-License-Identifier: Apache-2.0 @@ -15,6 +17,7 @@ # next.js /.next/ +/.swc/ /out/ # production diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json deleted file mode 100644 index 0d85e2c..0000000 --- a/frontend/.eslintrc.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "extends": "next/core-web-vitals", - "rules": { - // --- Linting --- - "no-debugger": "warn", - "no-console": "warn", - // use direct imports on material-ui to improve - // performance in unit tests with jest - // see https://blog.bitsrc.io/why-is-my-jest-suite-so-slow-2a4859bb9ac0 - "no-restricted-imports": [ - "warn", - { - "name": "@mui/material", - "message": "Please use \"import foo from '@mui/material/foo'\" instead." - } - ], - // do not warn for use of img element - "@next/next/no-img-element": "off", - // --- Formating --- - "eol-last": [ - "warn", - "always" - ], - "quotes": [ - "warn", - "single" - ], - "semi": [ - "warn", - "never" - ], - "indent": [ - "warn", - 2, - { - "SwitchCase": 1 - } - ], - "no-trailing-spaces": "warn", - "no-multi-spaces": [ - "warn" - ], - "no-multiple-empty-lines": "warn", - "object-curly-spacing": [ - "warn", - "never" - ], - "array-bracket-spacing": [ - "warn", - "never" - ] - } -} \ No newline at end of file diff --git a/frontend/.eslintrc.json.license b/frontend/.eslintrc.json.license deleted file mode 100644 index 906822e..0000000 --- a/frontend/.eslintrc.json.license +++ /dev/null @@ -1,7 +0,0 @@ -SPDX-FileCopyrightText: 2021 - 2023 Dusan Mijatovic (dv4all) -SPDX-FileCopyrightText: 2021 - 2023 dv4all -SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -SPDX-FileCopyrightText: 2023 Netherlands eScience Center - -SPDX-License-Identifier: Apache-2.0 -SPDX-License-Identifier: CC-BY-4.0 diff --git a/frontend/.knip.jsonc b/frontend/.knip.jsonc new file mode 100644 index 0000000..8dd96ac --- /dev/null +++ b/frontend/.knip.jsonc @@ -0,0 +1,25 @@ +{ + "ignore":[ + // ignore mocks as some are not imported/used yet but can be in the future + "**/__mocks__/**", + // ignore jest setup file + "jest.setup.js", + // these are "required" in next.config.js but knip does not see that :-( + "next.headers.js", + "next.rewrites.js" + ], + // see https://knip.dev/reference/configuration#ignoreexportsusedinfile + "ignoreExportsUsedInFile": { + "interface": true, + "type": true + }, + // see https://knip.dev/reference/configuration#includeentryexports + "includeEntryExports": true, + // see https://knip.dev/guides/handling-issues#unreachable-code + "ignoreDependencies": [ + // ignore tailwind plugin + "@tailwindcss/typography", + // ignore sharp used in next standalone mode + "sharp" + ] +} \ No newline at end of file diff --git a/frontend/.unimportedrc.json b/frontend/.unimportedrc.json deleted file mode 100644 index ec4eedd..0000000 --- a/frontend/.unimportedrc.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "ignorePatterns": [ - "**/node_modules/**", - "**/*.tests.{js,jsx,ts,tsx}", - "**/*.test.{js,jsx,ts,tsx}", - "**/*.spec.{js,jsx,ts,tsx}", - "**/tests/**", - "**/__tests__/**", - "**/*.d.ts", - "**/coverage/**", - "**/__mocks__/**", - "utils/jest/**" - ], - "ignoreUnimported": [ - "jest.config.js", - "jest.setup.js", - "next.config.js", - "next.headers.js", - "next.rewrites.js", - "postcss.config.js", - "tailwind.config.js" - ], - "ignoreUnused": [ - "next", - "react", - "react-dom", - "@tailwindcss/typography", - "sharp" - ], - "ignoreUnresolved": [ - "pages/api/fe/auth" - ] -} \ No newline at end of file diff --git a/frontend/.unimportedrc.json.license b/frontend/.unimportedrc.json.license deleted file mode 100644 index 546d6c9..0000000 --- a/frontend/.unimportedrc.json.license +++ /dev/null @@ -1,4 +0,0 @@ -SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -SPDX-FileCopyrightText: 2023 Netherlands eScience Center - -SPDX-License-Identifier: Apache-2.0 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 94c8a74..819eb98 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,7 +1,8 @@ # SPDX-FileCopyrightText: 2021 - 2022 Dusan Mijatovic (dv4all) # SPDX-FileCopyrightText: 2021 - 2022 dv4all -# SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -# SPDX-FileCopyrightText: 2023 Netherlands eScience Center +# SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +# SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center +# SPDX-FileCopyrightText: 2024 Ewan Cahen (Netherlands eScience Center) # # SPDX-License-Identifier: Apache-2.0 @@ -12,14 +13,14 @@ # ---------------------------------------- # 1. Install dependencies only when needed # ---------------------------------------- -FROM node:20.15-slim AS deps +FROM node:22.13.1-bookworm-slim AS deps WORKDIR /app # copy COPY package.json package-lock.json ./ # install -RUN npm install --frozen-lockfile --silent +RUN npm ci --silent # ---------------------------------------- # 2. Build image @@ -39,7 +40,7 @@ COPY . . ENV NEXT_TELEMETRY_DISABLED 1 # unit tests are already runned on PR and push to main -# RUN npm test +# RUN npm run test # build the solution RUN npm run build @@ -47,7 +48,7 @@ RUN npm run build # ---------------------------------------- # 3. Production image (standalone mode) # ---------------------------------------- -FROM node:20.15-slim AS runner +FROM node:22.13.1-bookworm-slim AS runner # optional install updates # RUN apt-get upgrade -y @@ -75,4 +76,4 @@ ENV PORT 3000 # set next hostname ENV HOSTNAME "0.0.0.0" -CMD ["node", "server.js"] \ No newline at end of file +CMD ["node", "server.js"] diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev index f4155fd..9a3b00e 100644 --- a/frontend/Dockerfile.dev +++ b/frontend/Dockerfile.dev @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: Apache-2.0 -FROM node:20.15-slim +FROM node:22.13.1-bookworm-slim # Change the user and group id of the node user to the specified ids ARG DUID=1000 @@ -27,4 +27,4 @@ VOLUME [ "/app" ] EXPOSE 3000 -CMD [ "sh", "-c", "yarn install ; yarn dev:docker" ] +CMD [ "sh", "-c", "npm install ; npm run dev:docker" ] diff --git a/frontend/__tests__/CookiesPage.test.tsx b/frontend/__tests__/CookiesPage.test.tsx index db2892e..183381f 100644 --- a/frontend/__tests__/CookiesPage.test.tsx +++ b/frontend/__tests__/CookiesPage.test.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 dv4all -// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -13,6 +13,9 @@ import CookiesPage from '../pages/cookies' // use DEFAULT MOCK for login providers list // required when AppHeader component is used jest.mock('~/auth/api/useLoginProviders') +// MOCK global search +jest.mock('~/components/GlobalSearchAutocomplete/apiGlobalSearch') +jest.mock('~/components/GlobalSearchAutocomplete/useHasRemotes') it('renders cookies page with title Cookies', async() => { @@ -22,8 +25,8 @@ it('renders cookies page with title Cookies', async() => { } render(WrappedComponentWithProps( CookiesPage, { - props - })) + props + })) const heading = await screen.findByRole('heading',{ name: 'Cookies' }) @@ -39,8 +42,8 @@ it('renders cookies page with anonymous statistics checkbox ON', async() => { } render(WrappedComponentWithProps( CookiesPage, { - props - })) + props + })) const heading = await screen.findByRole('heading',{ name: 'Tracking cookies' @@ -61,8 +64,8 @@ it('renders cookies page with anonymous statistics checkbox OFF', async() => { } render(WrappedComponentWithProps( CookiesPage, { - props - })) + props + })) const checkbox = await screen.findByRole('checkbox', { checked:false diff --git a/frontend/__tests__/OrganisationPage.test.tsx b/frontend/__tests__/OrganisationPage.test.tsx index d8d0812..aba6465 100644 --- a/frontend/__tests__/OrganisationPage.test.tsx +++ b/frontend/__tests__/OrganisationPage.test.tsx @@ -1,8 +1,9 @@ // SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 dv4all -// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center +// SPDX-FileCopyrightText: 2025 Ewan Cahen (Netherlands eScience Center) // // SPDX-License-Identifier: Apache-2.0 @@ -18,22 +19,33 @@ import mockSoftware from '~/components/organisation/software/__mocks__/mockSoftw import mockProjects from '~/components/organisation/projects/__mocks__/mockProjects.json' import mockUnits from '~/components/organisation/units/__mocks__/mockUnits.json' import {TabKey} from '~/components/organisation/tabs/OrganisationTabItems' +import {OrganisationForContext} from '~/components/organisation/context/OrganisationContext' -// MOCK user agreement call -jest.mock('~/components/user/settings/useUserAgreements') +// mock user agreement call +jest.mock('~/components/user/settings/agreements/useUserAgreements') +// global search +jest.mock('~/components/GlobalSearchAutocomplete/apiGlobalSearch') +jest.mock('~/components/GlobalSearchAutocomplete/useHasRemotes') // use DEFAULT MOCK for login providers list // required when AppHeader component is used jest.mock('~/auth/api/useLoginProviders') +// mock project categories api +jest.mock('~/components/organisation/projects/filters/useOrgProjectCategoriesList') +// mock software categories api +jest.mock('~/components/organisation/software/filters/useOrgSoftwareCategoriesList') const mockProps = { - organisation: mockOrganisation, + organisation: mockOrganisation as OrganisationForContext, slug:['dutch-research-council'], - tab: 'software' as TabKey, + tab: 'projects' as TabKey, ror: mockRORIinfo as any, isMaintainer: false, rsd_page_rows: 12, - rsd_page_layout: 'grid' + rsd_page_layout: 'grid', + units: mockUnits, + releaseCountsByYear: [], + releases: [] } as OrganisationPageProps // MOCK isMaintainerOfOrganisation @@ -105,8 +117,7 @@ describe('pages/organisations/[...slug].tsx', () => { // screen.debug(aboutPage) }) - // disable software option, 2024-07-02 - // it('renders organisation software page as default when organisation.description=null', async () => { + // it('renders organisation project page as default when organisation.description=null', async () => { // // not a maintainer - public page // mockIsMaintainerOfOrganisation.mockResolvedValueOnce(false) // // when no about page content, software is default landing page @@ -127,10 +138,10 @@ describe('pages/organisations/[...slug].tsx', () => { // // wait loader to be removed // // await waitForElementToBeRemoved(screen.getByRole('progressbar')) // // we need to await for all events to run - // const software = await screen.findAllByTestId('software-grid-card') + // const software = await screen.findAllByTestId('project-grid-card') // expect(software.length).toEqual(mockSoftware.length) // // validate api call - TODO! FIGURE WHY IS CALLED TWICE!!! - // expect(mockSoftwareForOrganisation).toBeCalledTimes(1) + // expect(mockSoftwareForOrganisation).toHaveBeenCalledTimes(1) // }) it('renders organisation projects page when page=projects', async () => { @@ -158,7 +169,7 @@ describe('pages/organisations/[...slug].tsx', () => { const cards = await screen.findAllByTestId('project-grid-card') expect(cards.length).toEqual(mockProjects.length) // validate api call - TODO! FIGURE WHY IS CALLED TWICE!!! - expect(mockProjectsForOrganisation).toBeCalledTimes(1) + expect(mockProjectsForOrganisation).toHaveBeenCalledTimes(1) }) it('shows organisation units', async() => { @@ -182,7 +193,7 @@ describe('pages/organisations/[...slug].tsx', () => { await waitForElementToBeRemoved(screen.getByRole('progressbar')) // validate api call - expect(mockGetOrganisationChildren).toBeCalledTimes(1) + expect(mockGetOrganisationChildren).toHaveBeenCalledTimes(0) // validate units const units = screen.getAllByTestId('research-unit-item') expect(units.length).toEqual(mockUnits.length) diff --git a/frontend/__tests__/ProjectEditPage.test.tsx b/frontend/__tests__/ProjectEditPage.test.tsx index 397d155..5a9b81f 100644 --- a/frontend/__tests__/ProjectEditPage.test.tsx +++ b/frontend/__tests__/ProjectEditPage.test.tsx @@ -1,7 +1,7 @@ -// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) -// SPDX-FileCopyrightText: 2023 Netherlands eScience Center // SPDX-FileCopyrightText: 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 @@ -21,7 +21,10 @@ import mockProjectToEdit from '~/components/projects/edit/information/__mocks__/ // we mock default providers used in page header jest.mock('~/auth/api/useLoginProviders') // mock user agreement call -jest.mock('~/components/user/settings/useUserAgreements') +jest.mock('~/components/user/settings/agreements/useUserAgreements') +// global search +jest.mock('~/components/GlobalSearchAutocomplete/apiGlobalSearch') +jest.mock('~/components/GlobalSearchAutocomplete/useHasRemotes') // MOCK isMaintainerOf const mockIsMaintainer = jest.fn(props => Promise.resolve(false)) @@ -44,7 +47,6 @@ window.IntersectionObserver = jest.fn(() => ({ unobserve: mockUnobserve, } as any)) - const mockProps = { // information page pageIndex: 0, diff --git a/frontend/__tests__/ProjectItem.test.tsx b/frontend/__tests__/ProjectItem.test.tsx index 047dc42..9167e30 100644 --- a/frontend/__tests__/ProjectItem.test.tsx +++ b/frontend/__tests__/ProjectItem.test.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 dv4all -// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -40,7 +40,11 @@ const mockedProps: ProjectPageProps = { impact: apiMentions as MentionItemProps[], team: apiContributors, relatedProjects: apiRelatedProjects as RelatedProject[], - relatedSoftware: apiRelatedSoftware as any + relatedSoftware: apiRelatedSoftware as any, + testimonials:[], + categories:[], + orgMaintainer:[], + comMaintainer:[] } describe('pages/projects/[slug]/index.tsx', () => { diff --git a/frontend/__tests__/ProjectsOverviewPage.test.tsx b/frontend/__tests__/ProjectsOverviewPage.test.tsx index d11e7d5..e9a2e6c 100644 --- a/frontend/__tests__/ProjectsOverviewPage.test.tsx +++ b/frontend/__tests__/ProjectsOverviewPage.test.tsx @@ -1,16 +1,14 @@ -// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2023 Netherlands eScience Center // SPDX-FileCopyrightText: 2023 dv4all // // SPDX-License-Identifier: Apache-2.0 import {render, screen, within} from '@testing-library/react' -import {WithAppContext} from '~/utils/jest/WithAppContext' +import {WithAppContext, defaultUserSettings} from '~/utils/jest/WithAppContext' import ProjectOverviewPage from '../pages/projects/index' -import {LayoutType} from '~/components/software/overview/search/ViewToggleGroup' - import mockData from './__mocks__/projectsOverview.json' // use DEFAULT MOCK for login providers list @@ -27,7 +25,6 @@ const mockProps = { page: 1, rows: 12, count: 408, - layout: 'masonry' as LayoutType, keywordsList: mockData.keywordsList, domainsList: mockData.domainsList, organisationsList: mockData.organisationsList, @@ -35,7 +32,6 @@ const mockProps = { projects: mockData.projects as any } - describe('pages/projects/index.tsx', () => { beforeEach(() => { @@ -44,7 +40,7 @@ describe('pages/projects/index.tsx', () => { it('renders title All projects', () => { render( - + ) @@ -56,24 +52,24 @@ describe('pages/projects/index.tsx', () => { it('renders project filter panel with orderBy, project status and 3 other filters (combobox)', () => { render( - + ) // get reference to filter panel const panel = screen.getByTestId('filters-panel') // find order by testid - const order = within(panel).getByTestId('filters-order-by') - const status = within(panel).getByTestId('filters-project-status') - // should have 3 filters + // within(panel).getByTestId('filters-order-by') + // within(panel).getByTestId('filters-project-status') + // should have 5 filters const filters = within(panel).getAllByRole('combobox') - expect(filters.length).toEqual(3) + expect(filters.length).toEqual(5) // screen.debug(filters) }) it('renders searchbox with placeholder Find project', async () => { render( - + ) @@ -81,9 +77,8 @@ describe('pages/projects/index.tsx', () => { }) it('renders layout options (toggle button group)', async () => { - mockProps.layout='masonry' render( - + ) @@ -91,9 +86,9 @@ describe('pages/projects/index.tsx', () => { }) it('renders (12) grid cards (even for masonry layout type)', async () => { - mockProps.layout='masonry' + defaultUserSettings.rsd_page_layout='masonry' render( - + ) @@ -102,9 +97,9 @@ describe('pages/projects/index.tsx', () => { }) it('renders (12) list items', async () => { - mockProps.layout='list' + defaultUserSettings.rsd_page_layout='list' render( - + ) diff --git a/frontend/__tests__/SoftwareEditPage.test.tsx b/frontend/__tests__/SoftwareEditPage.test.tsx index 7465482..573337e 100644 --- a/frontend/__tests__/SoftwareEditPage.test.tsx +++ b/frontend/__tests__/SoftwareEditPage.test.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2023 - 2024 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 - 2024 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // SPDX-FileCopyrightText: 2023 dv4all @@ -23,7 +23,10 @@ import {RsdSettingsState} from '~/config/rsdSettingsReducer' // we mock default providers used in page header jest.mock('~/auth/api/useLoginProviders') // mock user agreement call -jest.mock('~/components/user/settings/useUserAgreements') +jest.mock('~/components/user/settings/agreements/useUserAgreements') +// MOCK global search +jest.mock('~/components/GlobalSearchAutocomplete/apiGlobalSearch') +jest.mock('~/components/GlobalSearchAutocomplete/useHasRemotes') // MOCK isMaintainerOf const mockIsMaintainer = jest.fn(props => Promise.resolve(false)) diff --git a/frontend/__tests__/SoftwareItem.test.tsx b/frontend/__tests__/SoftwareItem.test.tsx deleted file mode 100644 index bab10f6..0000000 --- a/frontend/__tests__/SoftwareItem.test.tsx +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) -// SPDX-FileCopyrightText: 2022 dv4all -// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 Netherlands eScience Center -// -// SPDX-License-Identifier: Apache-2.0 - -import {render, screen} from '@testing-library/react' -import SoftwareItemPage from '../pages/software/[slug]/index' -import {WrappedComponentWithProps} from '../utils/jest/WrappedComponents' - -// mock fetch response -import softwareIndexData from './__mocks__/softwareIndexData' - -// use DEFAULT MOCK for login providers list -// required when AppHeader component is used -jest.mock('~/auth/api/useLoginProviders') - -jest.mock('../utils/getSoftware') - -// mock next router -const mockBack = jest.fn() -const mockReplace = jest.fn() -const mockPush = jest.fn() -jest.mock('next/router', () => ({ - useRouter: () => ({ - back: mockBack, - replace: mockReplace, - push: mockPush - }) -})) - -describe('pages/software/[slug]/index.tsx', () => { - it('renders heading with software title', async() => { - render(WrappedComponentWithProps( - SoftwareItemPage, - {props: softwareIndexData} - )) - const title = await screen.findByText(softwareIndexData.software.brand_name) - expect(title).toBeInTheDocument() - // screen.debug() - }) - it('renders edit button when isMaintainer=true', () => { - // set isMaintainer to true - softwareIndexData.isMaintainer=true - render(WrappedComponentWithProps( - SoftwareItemPage, - {props: softwareIndexData} - )) - const editBtn = screen.getByTestId('edit-button') - expect(editBtn).toBeInTheDocument() - // screen.debug(editBtn) - }) - it('DOES NOT render edit button when isMaintainer=false', () => { - // set isMaintainer to true - softwareIndexData.isMaintainer=false - render(WrappedComponentWithProps( - SoftwareItemPage, - {props: softwareIndexData} - )) - const editBtn = screen.queryByTestId('edit-button') - expect(editBtn).not.toBeInTheDocument() - // screen.debug(editBtn) - }) - it('render mentions count', () => { - render(WrappedComponentWithProps( - SoftwareItemPage, - {props: softwareIndexData} - )) - const expected = softwareIndexData.mentions.length - const mentions = screen.getByText(expected) - expect(mentions).toBeInTheDocument() - // screen.debug(editBtn) - }) - it('render contributor_cnt count', () => { - render(WrappedComponentWithProps( - SoftwareItemPage, - {props: softwareIndexData} - )) - const expected = softwareIndexData.contributors.length - const contributor_cnt = screen.getByText(expected) - expect(contributor_cnt).toBeInTheDocument() - // screen.debug(editBtn) - }) -}) diff --git a/frontend/__tests__/SoftwareOverview.test.tsx b/frontend/__tests__/SoftwareOverview.test.tsx index 51af8f5..e0053e2 100644 --- a/frontend/__tests__/SoftwareOverview.test.tsx +++ b/frontend/__tests__/SoftwareOverview.test.tsx @@ -1,15 +1,14 @@ // SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 dv4all -// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 import {render, screen, within} from '@testing-library/react' -import {WithAppContext} from '~/utils/jest/WithAppContext' +import {WithAppContext,defaultUserSettings} from '~/utils/jest/WithAppContext' import SoftwareOverviewPage from '../pages/software/index' -import {LayoutType} from '~/components/software/overview/search/ViewToggleGroup' // use DEFAULT MOCK for login providers list // required when AppHeader component is used @@ -23,16 +22,19 @@ const mockProps = { keywords:null, prog_lang: null, licenses:null, - order:null, + sources: null, + order: '', page: 1, rows: 12, count: 408, - layout: 'masonry' as LayoutType, keywordsList: mockData.keywordsList, languagesList: mockData.languagesList, licensesList: mockData.licensesList, + sourcesList: [], + hostsList: [], software: mockData.software as any, - highlights: mockData.highlights as any + highlights: mockData.highlights as any, + hasRemotes: false, } describe('pages/software/index.tsx', () => { @@ -57,7 +59,7 @@ describe('pages/software/index.tsx', () => { ) - const carousel = screen.getByTestId('highlights-carousel') + screen.getByTestId('highlights-carousel') const cards = screen.getAllByTestId('highlights-card') expect(cards.length).toEqual(mockData.highlights.length) }) @@ -71,10 +73,10 @@ describe('pages/software/index.tsx', () => { // get reference to filter panel const panel = screen.getByTestId('filters-panel') // find order by testid - const order = within(panel).getByTestId('filters-order-by') - // should have 3 filters + // within(panel).getByTestId('filters-order-by') + // should have 4 filters const filters = within(panel).getAllByRole('combobox') - expect(filters.length).toEqual(3) + expect(filters.length).toEqual(4) // screen.debug(filters) }) @@ -88,19 +90,19 @@ describe('pages/software/index.tsx', () => { }) it('renders layout options (toggle button group)', async () => { - mockProps.layout='masonry' + defaultUserSettings.rsd_page_layout='masonry' render( - + ) - const buttonGroup = screen.getByTestId('card-layout-options') + screen.getByTestId('card-layout-options') }) it('renders (12) masonry cards', async () => { - mockProps.layout='masonry' + defaultUserSettings.rsd_page_layout='masonry' render( - + ) @@ -109,9 +111,9 @@ describe('pages/software/index.tsx', () => { }) it('renders (12) grid cards', async () => { - mockProps.layout='grid' + defaultUserSettings.rsd_page_layout='grid' render( - + ) @@ -120,9 +122,9 @@ describe('pages/software/index.tsx', () => { }) it('renders (12) list items', async () => { - mockProps.layout='list' + defaultUserSettings.rsd_page_layout='list' render( - + ) diff --git a/frontend/__tests__/UserPages.test.tsx b/frontend/__tests__/UserPages.test.tsx index 4aaef4d..e428c72 100644 --- a/frontend/__tests__/UserPages.test.tsx +++ b/frontend/__tests__/UserPages.test.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2023 - 2024 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 - 2024 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // SPDX-FileCopyrightText: 2023 Ewan Cahen (Netherlands eScience Center) @@ -8,7 +8,7 @@ // SPDX-License-Identifier: Apache-2.0 import {render, screen} from '@testing-library/react' -import {WithAppContext, mockSession} from '~/utils/jest/WithAppContext' +import {WithAppContext, mockSession, defaultUserSettings} from '~/utils/jest/WithAppContext' import UserPages from '../pages/user/[section]' @@ -16,34 +16,52 @@ import mockSoftwareByMaintainer from '~/components/user/software/__mocks__/softw import mockProjectsByMaintainer from '~/components/user/project/__mocks__/projectsByMaintainer.json' import mockOrganisationsByMaintainer from '~/components/user/organisations/__mocks__/organisationsByMaintainer.json' import mockCommunitiesByMaintainer from '~/components/user/communities/__mocks__/communitiesByMaintainer.json' +import loginForAccount from '~/components/user/settings/profile/__mocks__/logins.json' +import {UserPageId} from '~/components/user/tabs/UserTabItems' +import {getDisplayName} from '~/utils/getDisplayName' +import { LoginForAccount } from '~/components/user/settings/profile/apiLoginForAccount' +// import {UserPageId} from '~/components/user/UserNavItems' // use DEFAULT MOCK for login providers list // required when AppHeader component is used jest.mock('~/auth/api/useLoginProviders') -// MOCK user agreement call -jest.mock('~/components/user/settings/useUserAgreements') +// mock user agreement call +jest.mock('~/components/user/settings/agreements/useUserAgreements') // MOCK user logins call -jest.mock('~/components/user/settings/useLoginForAccount') +jest.mock('~/components/user/settings/profile/useLoginForUser') // MOCK user project list jest.mock('~/components/user/project/useUserProjects') // MOCK user software list jest.mock('~/components/user/software/useUserSoftware') -// MOCK user software list -jest.mock('~/components/user/software/useUserSoftware') // MOCK user organisation list jest.mock('~/components/user/organisations/useUserOrganisations') // MOCK user communities list jest.mock('~/components/user/communities/useUserCommunities') +// MOCK global search +jest.mock('~/components/GlobalSearchAutocomplete/apiGlobalSearch') +jest.mock('~/components/GlobalSearchAutocomplete/useHasRemotes') // MOCKS const mockProps = { - section: 'software', + section: 'software' as UserPageId, counts: { software_cnt: 0, project_cnt: 0, organisation_cnt: 0, community_cnt: 0 }, - orcidAuthLink:null + orcidAuthLink:null, + linkedInAuthLink: null, + profile:{ + account: 'test-account-id', + given_names: 'Test given names', + family_names: 'Test family names', + email_address: null, + role: null, + affiliation: null, + is_public: false, + avatar_id: null + }, + logins:loginForAccount as LoginForAccount[] } describe('pages/user/[section].tsx', () => { @@ -64,52 +82,87 @@ describe('pages/user/[section].tsx', () => { expect(p401).toBeInTheDocument() }) - it('renders user nav items', () => { + it('renders user name', () => { mockProps.section = 'software' + const userName = getDisplayName(mockProps.profile) ?? 'No name' render( ) - const navItems = screen.getAllByTestId('user-nav-item') - // KIN-RPD has 4 items - expect(navItems.length).toEqual(4) + screen.getByText(RegExp(userName)) + // expect(navItems.length).toEqual(5) }) - it('renders user settings section', async() => { + it('renders user settings with User profile heading', async() => { mockProps.section = 'settings' render( ) - // settings page - const settings = await screen.findByTestId('user-settings-section') + await screen.findByTestId('user-profile-page-title') + // await screen.findByRole('heading',{name:'User profile'}) // shows user account if (mockSession.user?.account) { - const userId = screen.getByText(RegExp(mockSession.user?.account)) + screen.getByText(RegExp(mockSession.user?.account)) } }) - it('renders user software section', async() => { + it('renders user software list items', async() => { mockProps.section = 'software' + defaultUserSettings.rsd_page_layout = 'list' + render( + + + + ) + + // validate software cards are shown + const software = await screen.findAllByTestId('software-list-item') + expect(software.length).toEqual(mockSoftwareByMaintainer.length) + }) + it('renders user software grid items', async() => { + mockProps.section = 'software' + defaultUserSettings.rsd_page_layout = 'grid' render( - + ) // validate software cards are shown - const software = screen.getAllByTestId('software-list-item') + const software = await screen.findAllByTestId('software-grid-card') expect(software.length).toEqual(mockSoftwareByMaintainer.length) }) - it('renders user projects section', async() => { + + it('renders user projects list items', async() => { mockProps.section = 'projects' + defaultUserSettings.rsd_page_layout = 'list' + render( + + + + ) + + // validate project cards are shown + const project = await screen.findAllByTestId('project-list-item') + expect(project.length).toEqual(mockProjectsByMaintainer.length) + }) + it('renders user projects grid items', async() => { + mockProps.section = 'projects' + defaultUserSettings.rsd_page_layout = 'grid' render( @@ -117,35 +170,79 @@ describe('pages/user/[section].tsx', () => { ) // validate project cards are shown - const project = screen.getAllByTestId('project-list-item') + const project = await screen.findAllByTestId('project-grid-card') expect(project.length).toEqual(mockProjectsByMaintainer.length) }) - it('renders user organisation section', async() => { + it('renders user organisations list items', async() => { mockProps.section = 'organisations' + defaultUserSettings.rsd_page_layout = 'list' render( - + ) // validate project cards are shown - const project = screen.getAllByTestId('organisation-list-item') + const project = await screen.findAllByTestId('organisation-list-item') expect(project.length).toEqual(mockOrganisationsByMaintainer.length) }) - it('renders user communities section', async() => { - mockProps.section = 'communities' + it('renders user organisations grid items', async() => { + mockProps.section = 'organisations' + defaultUserSettings.rsd_page_layout = 'grid' render( - + ) // validate project cards are shown - const project = screen.getAllByTestId('community-list-item') + const project = await screen.findAllByTestId('organisation-card-link') + expect(project.length).toEqual(mockOrganisationsByMaintainer.length) + }) + + it('renders user communities list items', async() => { + mockProps.section = 'communities' + defaultUserSettings.rsd_page_layout = 'list' + + render( + + + + ) + + // validate community cards are shown + const project = await screen.findAllByTestId('community-list-item') + expect(project.length).toEqual(mockCommunitiesByMaintainer.length) + }) + + it('renders user communities card items', async() => { + mockProps.section = 'communities' + defaultUserSettings.rsd_page_layout = 'grid' + + render( + + + + ) + + // validate community cards are shown + const project = await screen.findAllByTestId('community-card-link') expect(project.length).toEqual(mockCommunitiesByMaintainer.length) }) diff --git a/frontend/__tests__/__mocks__/apiContributors.json b/frontend/__tests__/__mocks__/apiContributors.json index 4055cf3..42b2d29 100644 --- a/frontend/__tests__/__mocks__/apiContributors.json +++ b/frontend/__tests__/__mocks__/apiContributors.json @@ -10,7 +10,8 @@ "role": null, "orcid": null, "avatar_id": null, - "position": null + "position": null, + "account": null }, { "id": "c86ef6f6-5428-4142-b338-d8479611e5e6", @@ -23,7 +24,8 @@ "role": null, "orcid": null, "avatar_id": null, - "position": null + "position": null, + "account": null }, { "id": "27716d66-89f7-4f8f-b9a6-2a66ecf4762c", @@ -36,7 +38,8 @@ "role": null, "orcid": null, "avatar_id": null, - "position": null + "position": null, + "account": null }, { "id": "0a68cf00-649f-40b7-b469-d41760194844", @@ -49,7 +52,8 @@ "role": null, "orcid": null, "avatar_id": null, - "position": null + "position": null, + "account": null }, { "id": "d7f2c176-d188-49a4-8265-97a4e7456b47", @@ -62,7 +66,8 @@ "role": null, "orcid": null, "avatar_id": null, - "position": null + "position": null, + "account": null }, { "id": "0e9e77bd-f64a-4bb1-a4c9-362e77bb0495", @@ -75,7 +80,8 @@ "role": null, "orcid": null, "avatar_id": null, - "position": null + "position": null, + "account": null }, { "id": "6ed89f4b-62a9-41f1-811f-b88ed06da04c", @@ -88,7 +94,8 @@ "role": null, "orcid": null, "avatar_id": null, - "position": null + "position": null, + "account": null }, { "id": "d5e86fc3-79d8-47a1-83da-64ca0d0d8789", @@ -101,7 +108,8 @@ "role": null, "orcid": null, "avatar_id": null, - "position": null + "position": null, + "account": null }, { "id": "92df99ae-1945-4a8f-a8f2-6b94b492ee30", @@ -114,7 +122,8 @@ "role": null, "orcid": null, "avatar_id": null, - "position": null + "position": null, + "account": null }, { "id": "a7498680-2b2f-42d7-ac02-ba173a711c06", @@ -127,7 +136,8 @@ "role": null, "orcid": null, "avatar_id": null, - "position": null + "position": null, + "account": null }, { "id": "a07e253d-4edd-44d3-9d0c-3d25215bbf6c", @@ -140,7 +150,8 @@ "role": null, "orcid": null, "avatar_id": null, - "position": null + "position": null, + "account": null }, { "id": "21a54d99-aa96-4fe1-a84e-473c1f20a7a5", @@ -153,6 +164,7 @@ "role": null, "orcid": null, "avatar_id": null, - "position": null + "position": null, + "account": null } ] \ No newline at end of file diff --git a/frontend/auth/ProtectedContent.test.tsx b/frontend/auth/ProtectedContent.test.tsx index 893b36d..1b3a329 100644 --- a/frontend/auth/ProtectedContent.test.tsx +++ b/frontend/auth/ProtectedContent.test.tsx @@ -1,5 +1,7 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2024 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2024 - 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -10,7 +12,9 @@ import {WrappedComponentWithProps} from '../utils/jest/WrappedComponents' import ProtectedContent from './ProtectedContent' // MOCKS +// eslint-disable-next-line @typescript-eslint/no-unused-vars const softwareMaintainer=jest.fn((props)=>Promise.resolve(false)) +// eslint-disable-next-line @typescript-eslint/no-unused-vars const projectMaintainer=jest.fn((props)=>Promise.resolve(false)) jest.mock('./permissions/isMaintainerOfSoftware', () => { // console.log('mocked isMaintainerOfSoftware') @@ -139,9 +143,9 @@ it('renders content of type software when maintainer', async () => { expect(content).toBeInTheDocument() // ensure isMaintainerOfSoftware is called - expect(softwareMaintainer).toBeCalledTimes(1) + expect(softwareMaintainer).toHaveBeenCalledTimes(1) // with expected agrguments - expect(softwareMaintainer).toBeCalledWith({ + expect(softwareMaintainer).toHaveBeenCalledWith({ 'account': 'test-account-string', 'slug': '/test-software-1', 'token': 'TEST_RANDOM_TOKEN', @@ -174,9 +178,9 @@ it('renders content of type project when maintainer', async () => { expect(content).toBeInTheDocument() // ensure isMaintainerOfProject is called - expect(projectMaintainer).toBeCalledTimes(1) + expect(projectMaintainer).toHaveBeenCalledTimes(1) // with expected agrguments - expect(projectMaintainer).toBeCalledWith({ + expect(projectMaintainer).toHaveBeenCalledWith({ 'account': 'test-account-string', 'slug': '/test-project-1', 'token': 'TEST_RANDOM_TOKEN', diff --git a/frontend/auth/api/authEndpoint.ts b/frontend/auth/api/authEndpoint.ts index 7c3ca52..d9e9620 100644 --- a/frontend/auth/api/authEndpoint.ts +++ b/frontend/auth/api/authEndpoint.ts @@ -7,7 +7,7 @@ import logger from '~/utils/logger' import {getAuthorisationEndpoint} from './authHelpers' -type providers = 'surfconext'|'helmholtzid'|'orcid'|'azure'|'linkedin' +export type ProviderName = 'surfconext'|'helmholtz'|'orcid'|'azure'|'linkedin'|'local' // how often we refresh auth endpoint const refreshInterval = 60*60*1000 // save timer as public variable @@ -25,7 +25,7 @@ const cache:{ * refreshInterval defined how often we refresh auth endpoint info. * */ -export async function getAuthEndpoint(wellknownUrl:string,provider:providers){ +export async function getAuthEndpoint(wellknownUrl:string,provider:ProviderName){ try{ // if already present return existing value if (cache?.[provider]?.authEndpoint) { diff --git a/frontend/auth/api/authHelpers.test.ts b/frontend/auth/api/authHelpers.test.ts index 1a777fa..9d34539 100644 --- a/frontend/auth/api/authHelpers.test.ts +++ b/frontend/auth/api/authHelpers.test.ts @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all -// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center +// SPDX-FileCopyrightText: 2025 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2025 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 @@ -45,6 +47,7 @@ const mockProps: RedirectToProps = { client_id: '1234567', scope: 'openid', response_mode: 'form', + prompt: 'login' } // mock console log @@ -63,12 +66,19 @@ beforeEach(() => { it('crates RedirectUrl', () => { - const {authorization_endpoint, redirect_uri, client_id, scope, response_mode} = mockProps - const expectedRedirect = `${authorization_endpoint}?redirect_uri=${redirect_uri}&client_id=${client_id}&scope=${scope}&response_type=code&response_mode=${response_mode}&prompt=login+consent` + const {authorization_endpoint, redirect_uri, client_id, scope, response_mode, prompt} = mockProps + const expectedRedirect = `${authorization_endpoint}?redirect_uri=${redirect_uri}&client_id=${client_id}&scope=${scope}&response_type=code&response_mode=${response_mode}&prompt=${prompt}` const redirectUrl = getRedirectUrl(mockProps) expect(redirectUrl).toEqual(expectedRedirect) }) +it('does not enforce prompt parameter', () => { + const {authorization_endpoint, redirect_uri, client_id, scope, response_mode} = mockProps + const expectedRedirect = `${authorization_endpoint}?redirect_uri=${redirect_uri}&client_id=${client_id}&scope=${scope}&response_type=code&response_mode=${response_mode}` + const redirectUrl = getRedirectUrl({...mockProps, prompt: undefined}) + expect(redirectUrl).toEqual(expectedRedirect) +}) + it('returns authorization_endpoint', async() => { mockResolvedValueOnce(mockWellKnowResp) const authorization_endpoint = await getAuthorisationEndpoint('mockedWellKnowEndpoint') @@ -133,15 +143,15 @@ it('claimProjectMaintainerInvite calls expected endpoint', async () => { }) // claim maintainer invite - const response = await claimProjectMaintainerInvite({ + await claimProjectMaintainerInvite({ id: expectedId, token: 'TEST_TOKEN', frontend: true }) // validate api call - expect(global.fetch).toBeCalledTimes(1) - expect(global.fetch).toBeCalledWith(expectedUrl,{ + expect(global.fetch).toHaveBeenCalledTimes(1) + expect(global.fetch).toHaveBeenCalledWith(expectedUrl,{ body: `{"invitation":"${expectedId}"}`, headers: { 'Accept': 'application/vnd.pgrst.object + json', @@ -211,15 +221,15 @@ it('claimSoftwareMaintainerInvite calls expected endpoint', async () => { }) // claim maintainer invite - const response = await claimSoftwareMaintainerInvite({ + await claimSoftwareMaintainerInvite({ id: expectedId, token: 'TEST_TOKEN', frontend: true }) // validate api call - expect(global.fetch).toBeCalledTimes(1) - expect(global.fetch).toBeCalledWith(expectedUrl, { + expect(global.fetch).toHaveBeenCalledTimes(1) + expect(global.fetch).toHaveBeenCalledWith(expectedUrl, { body: `{"invitation":"${expectedId}"}`, headers: { 'Accept': 'application/vnd.pgrst.object + json', @@ -289,15 +299,15 @@ it('claimOrganisationMaintainerInvite calls expected endpoint', async () => { }) // claim maintainer invite - const response = await claimOrganisationMaintainerInvite({ + await claimOrganisationMaintainerInvite({ id: expectedId, token: 'TEST_TOKEN', frontend: true }) // validate api call - expect(global.fetch).toBeCalledTimes(1) - expect(global.fetch).toBeCalledWith(expectedUrl, { + expect(global.fetch).toHaveBeenCalledTimes(1) + expect(global.fetch).toHaveBeenCalledWith(expectedUrl, { body: `{"invitation":"${expectedId}"}`, headers: { 'Accept': 'application/vnd.pgrst.object + json', diff --git a/frontend/auth/api/authHelpers.ts b/frontend/auth/api/authHelpers.ts index ac03ff3..bbb8bb1 100644 --- a/frontend/auth/api/authHelpers.ts +++ b/frontend/auth/api/authHelpers.ts @@ -3,6 +3,8 @@ // SPDX-FileCopyrightText: 2023 - 2024 Dusan Mijatovic (Netherlands eScience Center) // SPDX-FileCopyrightText: 2023 - 2024 Netherlands eScience Center // SPDX-FileCopyrightText: 2024 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2025 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2025 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 @@ -42,8 +44,11 @@ export function getRedirectUrl(props: RedirectToProps) { '&client_id=' + props.client_id + '&scope=' + props.scope + '&response_type=code' + - '&response_mode=' + props.response_mode + - '&prompt=' + (props.prompt ? props.prompt : 'login+consent') + '&response_mode=' + props.response_mode + + if (props?.prompt) { + redirectUrl += '&prompt=' + props.prompt + } if (props?.claims){ redirectUrl += '&claims=' + encodeURIComponent(JSON.stringify(props.claims)) @@ -190,7 +195,7 @@ export async function claimCommunityMaintainerInvite({id, token}: { id: string, token?: string}) { try { const query = 'rpc/accept_invitation_community' - let url = `${getBaseUrl()}/${query}` + const url = `${getBaseUrl()}/${query}` const resp = await fetch(url, { method: 'POST', diff --git a/frontend/auth/api/getLoginProviders.tsx b/frontend/auth/api/getLoginProviders.tsx new file mode 100644 index 0000000..a1a32e2 --- /dev/null +++ b/frontend/auth/api/getLoginProviders.tsx @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2025 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import {Provider} from 'pages/api/fe/auth' +import logger from '~/utils/logger' + +// save info after initial call +let loginProviders:Provider[] = [] + +export async function getLoginProviders(baseUrl?:string) { + try{ + // console.group('getLoginProviders') + // console.log('baseUrl...', baseUrl) + // console.log('loginProviders...', loginProviders) + // console.groupEnd() + + if (loginProviders.length === 0){ + // baseUrl is required for requests from server side + // this is internal url of frontend, by default this + // is http://localhost:3000 + const url = `${baseUrl ?? ''}/api/fe/auth` + // console.log('url...', url) + const resp = await fetch(url) + + if (resp.status === 200){ + const providers: Provider[] = await resp.json() + // api response is the same once the app is started + // because the info eventually comes from .env file + // to avoid additional api calls we save api response + // into the loginProviders variable and reuse it + loginProviders = [ + ...providers + ] + }else{ + logger(`getLoginProviders: ${resp.status}: ${resp.statusText}`, 'warn') + } + } + return loginProviders + }catch(e:any){ + logger(`getLoginProviders: ${e.message}`, 'error') + return loginProviders + } +} diff --git a/frontend/auth/api/useLoginProviders.tsx b/frontend/auth/api/useLoginProviders.tsx index f1c4c8b..cf330f0 100644 --- a/frontend/auth/api/useLoginProviders.tsx +++ b/frontend/auth/api/useLoginProviders.tsx @@ -1,51 +1,31 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all -// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 import {useEffect, useState} from 'react' import {Provider} from 'pages/api/fe/auth' - -// save info after initial call -let loginProviders:Provider[] = [] +import {getLoginProviders} from './getLoginProviders' export default function useLoginProviders() { const [providers, setProviders] = useState([]) // console.group('useLoginProviders') // console.log('providers...', providers) - // console.log('loginProviders...', loginProviders) // console.groupEnd() useEffect(() => { let abort = false - async function getProviders() { - if (loginProviders.length === 0){ - const url = '/api/fe/auth' - const resp = await fetch(url) - if (resp.status === 200 && abort === false) { - const providers: Provider[] = await resp.json() - if (abort) return - setProviders(providers) - // api response is the same once the app is started - // because the info eventually comes from .env file - // to avoid additional api calls we save api response - // into the loginProviders variable and reuse it - loginProviders = [ - ...providers - ] - } - }else if (abort===false){ - setProviders(loginProviders) - } - } - if (abort === false) { - getProviders() - } + getLoginProviders() + .then(providers=>{ + if (abort) return + setProviders(providers) + }) + .catch(()=>setProviders([])) return () => { abort = true } }, []) diff --git a/frontend/auth/index.tsx b/frontend/auth/index.tsx index 4d03dec..e73e733 100644 --- a/frontend/auth/index.tsx +++ b/frontend/auth/index.tsx @@ -1,15 +1,17 @@ // SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 dv4all -// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 - 2024 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2024 Netherlands eScience Center +// SPDX-FileCopyrightText: 2024 Christian Meeßen (GFZ) +// SPDX-FileCopyrightText: 2024 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // // SPDX-License-Identifier: Apache-2.0 import {createContext, Dispatch, SetStateAction, useState, useContext, useEffect} from 'react' -import verifyJwt, {decodeJwt} from './jwtUtils' import {IncomingMessage, OutgoingMessage} from 'http' -import cookie from 'cookie' -import logger from '../utils/logger' +import {parse} from 'cookie' +import logger from '~/utils/logger' +import verifyJwt, {decodeJwt} from './jwtUtils' import {refreshSession} from './refreshSession' // refresh schedule margin 5min. before expiration time @@ -17,6 +19,9 @@ import {refreshSession} from './refreshSession' const testMargin = process.env.REFRESH_MARGIN_MSEC ? parseInt(process.env.REFRESH_MARGIN_MSEC) : undefined export const REFRESH_MARGIN = testMargin ?? 5 * 60 * 1000 export type RsdRole = 'rsd_admin' | 'rsd_user' +export type RsdUserData = { + [property: string]: string[] +} export type RsdUser = { iss: 'rsd_auth' role: RsdRole @@ -25,7 +30,8 @@ export type RsdUser = { // uid account: string // display name - name: string + name: string, + data?: RsdUserData, } export type Session = { @@ -129,7 +135,7 @@ export function useSession(){ } /** - * Calculate expirition time from now in milliseconds + * Calculate expiration time from now in milliseconds * @param exp in seconds * @returns difference in milliseconds */ @@ -171,7 +177,7 @@ export function getSessionSeverSide(req: IncomingMessage|undefined, res: Outgoin // get token from cookie const token = getRsdTokenNode(req) // create session from token - const session = createSession(token) + const session = createSession(token ?? null) // remove invalid cookie if (session.status === 'invalid') { // console.log('remove rsd cookies...') @@ -193,7 +199,7 @@ export function getRsdTokenNode(req: IncomingMessage){ // check for cookies if (req?.headers?.cookie) { // parse cookies from node request - const cookies = cookie.parse(req.headers.cookie) + const cookies = parse(req.headers.cookie) // validate and decode const token = cookies?.rsd_token return token diff --git a/frontend/auth/permissions/isMaintainerOfCommunity.ts b/frontend/auth/permissions/isMaintainerOfCommunity.ts index 8dda42f..e84f63c 100644 --- a/frontend/auth/permissions/isMaintainerOfCommunity.ts +++ b/frontend/auth/permissions/isMaintainerOfCommunity.ts @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2024 Netherlands eScience Center +// SPDX-FileCopyrightText: 2024 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2024 - 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -63,13 +63,13 @@ export async function isMaintainerOfCommunity({community, account, token}: isCom } export async function getCommunitiesOfMaintainer({token}: - {token: string}) { + {token?: string}) { try { // without token api request is not needed if (!token) return [] // build url const query = 'rpc/communities_of_current_maintainer' - let url = `${getBaseUrl()}/${query}` + const url = `${getBaseUrl()}/${query}` const resp = await fetch(url, { method: 'GET', headers: createJsonHeaders(token) diff --git a/frontend/auth/permissions/isMaintainerOfOrganisation.test.ts b/frontend/auth/permissions/isMaintainerOfOrganisation.test.ts index 04ee77a..03fdcb1 100644 --- a/frontend/auth/permissions/isMaintainerOfOrganisation.test.ts +++ b/frontend/auth/permissions/isMaintainerOfOrganisation.test.ts @@ -1,5 +1,7 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2024 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2024 - 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -35,19 +37,11 @@ it('returns true when organisation in list', async () => { expect(isMaintainer).toBe(true) // validate call - expect(global.fetch).toBeCalledTimes(1) - expect(global.fetch).toBeCalledWith(expectedUrl, expectedOptions) + expect(global.fetch).toHaveBeenCalledTimes(1) + expect(global.fetch).toHaveBeenCalledWith(expectedUrl, expectedOptions) }) it('returns false when organisation NOT in list', async () => { - const expectedUrl = '/api/v1/rpc/organisations_of_current_maintainer' - const expectedOptions = { - 'headers': { - 'Authorization': `Bearer ${mockData.token}`, - 'Content-Type': 'application/json' - }, - 'method': 'GET' - } // return mocked organisation mockResolvedValueOnce([]) // get maintainer value @@ -68,9 +62,8 @@ it('makes call to expected rpc ', async () => { // return mocked organisation mockResolvedValueOnce([mockData.organisation]) // get maintainer value - const isMaintainer = await isMaintainerOfOrganisation(mockData) - + await isMaintainerOfOrganisation(mockData) // validate call - expect(global.fetch).toBeCalledTimes(1) - expect(global.fetch).toBeCalledWith(expectedUrl, expectedOptions) + expect(global.fetch).toHaveBeenCalledTimes(1) + expect(global.fetch).toHaveBeenCalledWith(expectedUrl, expectedOptions) }) diff --git a/frontend/auth/permissions/isMaintainerOfOrganisation.ts b/frontend/auth/permissions/isMaintainerOfOrganisation.ts index 57b9d71..bf228d4 100644 --- a/frontend/auth/permissions/isMaintainerOfOrganisation.ts +++ b/frontend/auth/permissions/isMaintainerOfOrganisation.ts @@ -1,12 +1,14 @@ // SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 dv4all -// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 -import {createJsonHeaders, getBaseUrl} from '../../utils/fetchHelpers' -import logger from '../../utils/logger' +import {OrganisationsOfProject} from '~/types/Project' +import {EditOrganisation, OrganisationsForSoftware, OrganisationSource} from '~/types/Organisation' +import {createJsonHeaders, getBaseUrl} from '~/utils/fetchHelpers' +import logger from '~/utils/logger' import {RsdRole} from '../index' type IsOrganisationMaintainerProps = { @@ -67,13 +69,13 @@ export async function isMaintainerOfOrganisation({organisation, account, token}: } export async function getMaintainerOrganisations({token}: - {token: string}) { + {token?: string}) { try { // without token api request is not needed if (!token) return [] // build url const query = 'rpc/organisations_of_current_maintainer' - let url = `${getBaseUrl()}/${query}` + const url = `${getBaseUrl()}/${query}` const resp = await fetch(url, { method: 'GET', headers: createJsonHeaders(token) @@ -92,5 +94,65 @@ export async function getMaintainerOrganisations({token}: } } +type CanEditOrganisationsProps={ + account?: string + token?: string + organisations: OrganisationsOfProject[]| OrganisationsForSoftware[] +} + +export async function canEditOrganisations({organisations,account,token}:CanEditOrganisationsProps){ + try{ + // collect isMaintainerRequests + const promises:Promise[] = [] + // prepare organisation list + const orgList = organisations.map((item, pos) => { + // save isMaintainer request + promises.push(isMaintainerOfOrganisation({ + organisation: item.id, + account, + token + })) + // extract only needed props + const org: EditOrganisation = { + ...item, + // additional props for edit type + position: pos + 1, + logo_b64: null, + logo_mime_type: null, + source: 'RSD' as OrganisationSource, + status: item.status, + // false by default + canEdit: false + } + return org + }) + // run all isMaintainer requests in parallel + const isMaintainer = await Promise.all(promises) + const canEditOrganisations = orgList.map((item, pos) => { + // update canEdit based on isMaintainer requests + if (isMaintainer[pos]) item.canEdit = isMaintainer[pos] + return item + }) + return canEditOrganisations + }catch(e:any){ + logger(`canEditOrganisations: ${e.message}`, 'error') + // on error all items set to false + return organisations.map((item, pos) => { + // extract only needed props + const org: EditOrganisation = { + ...item, + // additional props for edit type + position: pos + 1, + logo_b64: null, + logo_mime_type: null, + source: 'RSD' as OrganisationSource, + status: item.status, + // false by default + canEdit: false + } + return org + }) + } +} export default isMaintainerOfOrganisation diff --git a/frontend/auth/permissions/isMaintainerOfProject.test.ts b/frontend/auth/permissions/isMaintainerOfProject.test.ts index 1cc9280..80f8237 100644 --- a/frontend/auth/permissions/isMaintainerOfProject.test.ts +++ b/frontend/auth/permissions/isMaintainerOfProject.test.ts @@ -1,5 +1,7 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2024 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2024 - 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -34,10 +36,10 @@ it('calls expected rpc', async () => { // return mockResolvedValueOnce(expectedResp) // get maintainer value - const isMaintainer = await isMaintainerOfProject(mockData) + await isMaintainerOfProject(mockData) // validate call - expect(global.fetch).toBeCalledTimes(1) - expect(global.fetch).toBeCalledWith(expectedUrl, expectedOptions) + expect(global.fetch).toHaveBeenCalledTimes(1) + expect(global.fetch).toHaveBeenCalledWith(expectedUrl, expectedOptions) }) it('return isMaintainer true', async () => { diff --git a/frontend/auth/permissions/isMaintainerOfSoftware.test.ts b/frontend/auth/permissions/isMaintainerOfSoftware.test.ts index 32ba3cf..d3428c4 100644 --- a/frontend/auth/permissions/isMaintainerOfSoftware.test.ts +++ b/frontend/auth/permissions/isMaintainerOfSoftware.test.ts @@ -1,5 +1,7 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2024 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2024 - 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -34,10 +36,10 @@ it('calls expected rpc', async () => { // return mockResolvedValueOnce(expectedResp) // get maintainer value - const isMaintainer = await isMaintainerOfSoftware(mockData) + await isMaintainerOfSoftware(mockData) // validate call - expect(global.fetch).toBeCalledTimes(1) - expect(global.fetch).toBeCalledWith(expectedUrl, expectedOptions) + expect(global.fetch).toHaveBeenCalledTimes(1) + expect(global.fetch).toHaveBeenCalledWith(expectedUrl, expectedOptions) }) it('return isMaintainer true', async () => { diff --git a/frontend/pages/api/fe/auth/azure.ts b/frontend/auth/providers/azure.ts similarity index 70% rename from frontend/pages/api/fe/auth/azure.ts rename to frontend/auth/providers/azure.ts index 11773d9..05b62b4 100644 --- a/frontend/pages/api/fe/auth/azure.ts +++ b/frontend/auth/providers/azure.ts @@ -4,25 +4,21 @@ // SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2022 Matthias Rüster (GFZ) // SPDX-FileCopyrightText: 2022 dv4all -// SPDX-FileCopyrightText: 2023 - 2024 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) // // SPDX-License-Identifier: Apache-2.0 /** - * Azure Active Directory OpenID endpoint + * Azure Active Directory OpenID info * It provides frontend with redirect uri for the login button */ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type {NextApiRequest, NextApiResponse} from 'next' import logger from '~/utils/logger' import {RedirectToProps, getRedirectUrl} from '~/auth/api/authHelpers' import {getAuthEndpoint} from '~/auth/api/authEndpoint' -import {Provider, ApiError} from '.' -type Data = Provider | ApiError - -export async function azureRedirectProps() { +async function azureRedirectProps() { // extract wellknown url from env const wellknownUrl = process.env.AZURE_WELL_KNOWN_URL ?? null if (wellknownUrl) { @@ -59,35 +55,10 @@ export async function azureInfo() { const redirectUrl = getRedirectUrl(redirectProps) // provide redirectUrl and name/label return { - name: process.env.AZURE_DISPLAY_NAME || 'Azure Active Directory', + name: process.env.AZURE_DISPLAY_NAME ?? 'Azure Active Directory', redirectUrl, - html: process.env.AZURE_DESCRIPTION_HTML || 'Login with your institutional credentials.' + html: process.env.AZURE_DESCRIPTION_HTML ?? 'Login with your institutional credentials.' } } return null } - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - try { - // extract all props from env and wellknown endpoint - // and create return url and the name to use in login button - const loginInfo = await azureInfo() - if (loginInfo) { - res.status(200).json(loginInfo) - } else { - res.status(400).json({ - status: 400, - message: 'loginInfo missing' - }) - } - } catch (e: any) { - logger(`api/fe/auth/azure: ${e?.message}`, 'error') - res.status(500).json({ - status: 500, - message: e?.message - }) - } -} diff --git a/frontend/pages/api/fe/auth/helmholtzid.ts b/frontend/auth/providers/helmholtzid.ts similarity index 74% rename from frontend/pages/api/fe/auth/helmholtzid.ts rename to frontend/auth/providers/helmholtzid.ts index b9caff3..c5a3ac7 100644 --- a/frontend/pages/api/fe/auth/helmholtzid.ts +++ b/frontend/auth/providers/helmholtzid.ts @@ -3,25 +3,20 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 Matthias Rüster (GFZ) // SPDX-FileCopyrightText: 2022 dv4all -// SPDX-FileCopyrightText: 2023 - 2024 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) // SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center // SPDX-FileCopyrightText: 2025 Ewan Cahen (Netherlands eScience Center) // // SPDX-License-Identifier: Apache-2.0 /** - * Helmholz ID OpenID endpoint + * Helmholz ID OpenID info * It provides frontend with redirect uri for the login button */ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type {NextApiRequest, NextApiResponse} from 'next' import logger from '~/utils/logger' import {RedirectToProps, getRedirectUrl} from '~/auth/api/authHelpers' import {getAuthEndpoint} from '~/auth/api/authEndpoint' -import {Provider, ApiError} from '.' - -type Data = Provider | ApiError const claims = { id_token: { @@ -36,7 +31,7 @@ async function helmholtzRedirectProps() { const wellknownUrl = process.env.HELMHOLTZID_WELL_KNOWN_URL ?? null if (wellknownUrl) { // get (cached) authorisation endpoint from wellknown url - const authorization_endpoint = await getAuthEndpoint(wellknownUrl, 'helmholtzid') + const authorization_endpoint = await getAuthEndpoint(wellknownUrl, 'helmholtz') if (authorization_endpoint) { // construct all props needed for redirectUrl // use default values if env not provided @@ -78,28 +73,3 @@ export async function helmholtzInfo() { } return null } - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - try { - // extract all props from env and wellknown endpoint - // and create return url and the name to use in login button - const loginInfo = await helmholtzInfo() - if (loginInfo) { - res.status(200).json(loginInfo) - } else { - res.status(400).json({ - status: 400, - message: 'loginInfo missing' - }) - } - } catch (e: any) { - logger(`api/fe/auth/helmholtzid: ${e?.message}`, 'error') - res.status(500).json({ - status: 500, - message: e?.message - }) - } -} diff --git a/frontend/pages/api/fe/auth/linkedin.ts b/frontend/auth/providers/linkedin.ts similarity index 87% rename from frontend/pages/api/fe/auth/linkedin.ts rename to frontend/auth/providers/linkedin.ts index 22ee5ff..c901fb6 100644 --- a/frontend/pages/api/fe/auth/linkedin.ts +++ b/frontend/auth/providers/linkedin.ts @@ -1,26 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Dusan Mijatovic (Netherlands eScience Center) // SPDX-FileCopyrightText: 2025 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 +import logger from '~/utils/logger' import {getAuthEndpoint} from '~/auth/api/authEndpoint' import {getRedirectUrl, RedirectToProps} from '~/auth/api/authHelpers' -import logger from '~/utils/logger' - -export async function linkedinInfo() { - const redirectProps = await linkedinRedirectProps() - if (!redirectProps) { - return null - } - - const redirectUrl = getRedirectUrl(redirectProps) - - return { - name: 'LinkedIn', - redirectUrl, - html: 'Sign in with your LinkedIn account' - } -} async function linkedinRedirectProps() { try { @@ -57,8 +43,8 @@ async function linkedinRedirectProps() { authorization_endpoint, redirect_uri, client_id, - scope: 'openid%20profile%20email', - response_mode: 'code' + scope: process.env.LINKEDIN_SCOPES ?? 'openid%20profile%20email', + response_mode: process.env.LINKEDIN_RESPONSE_MODE ?? 'code' } return props } catch (e: any) { @@ -66,3 +52,18 @@ async function linkedinRedirectProps() { return null } } + +export async function linkedinInfo() { + const redirectProps = await linkedinRedirectProps() + if (!redirectProps) { + return null + } + + const redirectUrl = getRedirectUrl(redirectProps) + + return { + name: 'LinkedIn', + redirectUrl, + html: 'Sign in with your LinkedIn account.' + } +} diff --git a/frontend/auth/providers/local.ts b/frontend/auth/providers/local.ts new file mode 100644 index 0000000..e19b168 --- /dev/null +++ b/frontend/auth/providers/local.ts @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2022 - 2025 Netherlands eScience Center +// SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2025 Dusan Mijatovic (Netherlands eScience Center) +// +// SPDX-License-Identifier: Apache-2.0 + +export function localInfo() { + return { + name: 'Local account', + redirectUrl: '/login/local', + html: '

Sign in with local account is for testing purposes only. This option should not be enabled in the production version.

' + + } +} + diff --git a/frontend/pages/api/fe/auth/orcid.ts b/frontend/auth/providers/orcid.ts similarity index 69% rename from frontend/pages/api/fe/auth/orcid.ts rename to frontend/auth/providers/orcid.ts index 711fdde..509ef52 100644 --- a/frontend/pages/api/fe/auth/orcid.ts +++ b/frontend/auth/providers/orcid.ts @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2022 Matthias Rüster (GFZ) // SPDX-FileCopyrightText: 2022 dv4all -// SPDX-FileCopyrightText: 2023 - 2024 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) // // SPDX-License-Identifier: Apache-2.0 @@ -13,16 +13,11 @@ * It provides frontend with redirect uri for the login button */ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type {NextApiRequest, NextApiResponse} from 'next' import logger from '~/utils/logger' import {RedirectToProps, getRedirectUrl} from '~/auth/api/authHelpers' import {getAuthEndpoint} from '~/auth/api/authEndpoint' -import {Provider, ApiError} from '.' -type Data = Provider | ApiError - -export async function orcidRedirectProps() { +async function orcidRedirectProps() { try { // extract well known url from env const wellknownUrl = process.env.ORCID_WELL_KNOWN_URL ?? null @@ -66,36 +61,8 @@ export async function orcidInfo() { return { name: 'ORCID', redirectUrl, - html: ` - Sign in with ORCID is supported only for persons approved by the administrators. - Contact us if you wish to login with your ORCID. - ` + html: 'Sign in with your ORCID account.' } } return null } - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - try { - // extract all props from env and wellknown endpoint - // and create return url and the name to use in login button - const loginInfo = await orcidInfo() - if (loginInfo) { - res.status(200).json(loginInfo) - } else { - res.status(400).json({ - status: 400, - message: 'loginInfo missing' - }) - } - } catch (e: any) { - logger(`api/fe/auth/orcid: ${e?.message}`, 'error') - res.status(500).json({ - status: 500, - message: e?.message - }) - } -} diff --git a/frontend/pages/api/fe/auth/surfconext.ts b/frontend/auth/providers/surfconext.ts similarity index 72% rename from frontend/pages/api/fe/auth/surfconext.ts rename to frontend/auth/providers/surfconext.ts index 1ac58c6..60d8a45 100644 --- a/frontend/pages/api/fe/auth/surfconext.ts +++ b/frontend/auth/providers/surfconext.ts @@ -4,23 +4,17 @@ // SPDX-FileCopyrightText: 2022 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2022 Matthias Rüster (GFZ) // SPDX-FileCopyrightText: 2022 dv4all -// SPDX-FileCopyrightText: 2023 - 2024 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) // // SPDX-License-Identifier: Apache-2.0 /** - * SURFconext OpenID endpoint + * SURFconext OpenID info * It provides frontend with redirect uri for the login button */ - -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type {NextApiRequest, NextApiResponse} from 'next' import logger from '~/utils/logger' import {RedirectToProps, getRedirectUrl} from '~/auth/api/authHelpers' import {getAuthEndpoint} from '~/auth/api/authEndpoint' -import {Provider, ApiError} from '.' - -type Data = Provider | ApiError const claims = { id_token: { @@ -30,7 +24,7 @@ const claims = { } } -export async function surfconextRedirectProps() { +async function surfconextRedirectProps() { // extract wellknown url from env const wellknownUrl = process.env.SURFCONEXT_WELL_KNOWN_URL ?? null if (wellknownUrl) { @@ -75,28 +69,3 @@ export async function surfconextInfo() { } return null } - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - try { - // extract all props from env and wellknown endpoint - // and create return url and the name to use in login button - const loginInfo = await surfconextInfo() - if (loginInfo) { - res.status(200).json(loginInfo) - } else { - res.status(400).json({ - status: 400, - message: 'loginInfo missing' - }) - } - } catch (e: any) { - logger(`api/fe/auth/surfconext: ${e?.message}`, 'error') - res.status(500).json({ - status: 500, - message: e?.message - }) - } -} diff --git a/frontend/auth/refreshSession.test.ts b/frontend/auth/refreshSession.test.ts index 2f77959..5908f53 100644 --- a/frontend/auth/refreshSession.test.ts +++ b/frontend/auth/refreshSession.test.ts @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all -// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -37,5 +37,5 @@ it('returns null on error and logs error', async () => { // returns null for session expect(session).toEqual(null) // calls error log - expect(global.console.error).toBeCalledTimes(1) + expect(global.console.error).toHaveBeenCalledTimes(1) }) diff --git a/frontend/components/AppFooter/ContactEmail.tsx b/frontend/components/AppFooter/ContactEmail.tsx index fd1f2a1..77846c9 100644 --- a/frontend/components/AppFooter/ContactEmail.tsx +++ b/frontend/components/AppFooter/ContactEmail.tsx @@ -2,6 +2,8 @@ // SPDX-FileCopyrightText: 2022 - 2023 dv4all // SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) // SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences +// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2024 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -11,7 +13,7 @@ export default function ContactEmail({email, headers}:{email?:string, headers?:s if (email) { let mailTo = email if (headers && headers.length > 0) { - let encodedHeaders: string[] = [] + const encodedHeaders: string[] = [] headers.forEach(d => encodedHeaders.push(encodeURIComponent(d))) mailTo += '?' + headers.join('&') } diff --git a/frontend/components/AppFooter/index.tsx b/frontend/components/AppFooter/index.tsx index c35dee4..ffc8d06 100644 --- a/frontend/components/AppFooter/index.tsx +++ b/frontend/components/AppFooter/index.tsx @@ -19,7 +19,7 @@ export default function AppFooter () { return (