diff --git a/src/app/core/components/nav-menu/nav-menu.component.ts b/src/app/core/components/nav-menu/nav-menu.component.ts index 5b5ff3c46..f9d36bde5 100644 --- a/src/app/core/components/nav-menu/nav-menu.component.ts +++ b/src/app/core/components/nav-menu/nav-menu.component.ts @@ -12,12 +12,13 @@ import { toSignal } from '@angular/core/rxjs-interop'; import { ActivatedRoute, NavigationEnd, Router, RouterLink, RouterLinkActive } from '@angular/router'; import { MENU_ITEMS } from '@core/constants'; +import { ProviderSelectors } from '@core/store/provider'; import { filterMenuItems, updateMenuItems } from '@osf/core/helpers'; import { RouteContext } from '@osf/core/models'; import { AuthService } from '@osf/core/services'; import { UserSelectors } from '@osf/core/store/user'; import { IconComponent } from '@osf/shared/components'; -import { CurrentResourceType } from '@osf/shared/enums'; +import { CurrentResourceType, ReviewPermissions } from '@osf/shared/enums'; import { getViewOnlyParam } from '@osf/shared/helpers'; import { WrapFnPipe } from '@osf/shared/pipes'; import { CurrentResourceSelectors } from '@osf/shared/stores'; @@ -37,6 +38,7 @@ export class NavMenuComponent { private readonly isAuthenticated = select(UserSelectors.isAuthenticated); private readonly currentResource = select(CurrentResourceSelectors.getCurrentResource); + private readonly provider = select(ProviderSelectors.getCurrentProvider); readonly mainMenuItems = computed(() => { const isAuthenticated = this.isAuthenticated(); @@ -44,7 +46,7 @@ export class NavMenuComponent { const routeContext: RouteContext = { resourceId: this.currentResourceId(), - providerId: this.currentProviderId(), + providerId: this.provider()?.id, isProject: this.currentResource()?.type === CurrentResourceType.Projects && this.currentResourceId() === this.currentResource()?.id, @@ -53,6 +55,12 @@ export class NavMenuComponent { this.currentResourceId() === this.currentResource()?.id, isPreprint: this.isPreprintRoute(), preprintReviewsPageVisible: this.canUserViewReviews(), + registrationModerationPageVisible: + this.provider()?.type === CurrentResourceType.Registrations && + this.provider()?.permissions?.includes(ReviewPermissions.ViewSubmissions), + collectionModerationPageVisible: + this.provider()?.type === CurrentResourceType.Collections && + this.provider()?.permissions?.includes(ReviewPermissions.ViewSubmissions), isCollections: this.isCollectionsRoute() || false, currentUrl: this.router.url, isViewOnly: !!getViewOnlyParam(this.router), diff --git a/src/app/core/constants/nav-items.constant.ts b/src/app/core/constants/nav-items.constant.ts index c5cbe3a6b..5537cda8a 100644 --- a/src/app/core/constants/nav-items.constant.ts +++ b/src/app/core/constants/nav-items.constant.ts @@ -329,7 +329,7 @@ export const MENU_ITEMS: MenuItem[] = [ routerLink: 'moderation', label: 'navigation.moderation', visible: false, - routerLinkActiveOptions: { exact: true }, + routerLinkActiveOptions: { exact: false }, }, ], }, diff --git a/src/app/core/helpers/nav-menu.helper.ts b/src/app/core/helpers/nav-menu.helper.ts index 306801165..02b5a94a7 100644 --- a/src/app/core/helpers/nav-menu.helper.ts +++ b/src/app/core/helpers/nav-menu.helper.ts @@ -61,7 +61,7 @@ export function updateMenuItems(menuItems: MenuItem[], ctx: RouteContext): MenuI } if (item.id === 'collections') { - return { ...item, visible: ctx.isCollections }; + return updateCollectionMenuItem(item, ctx); } return item; @@ -137,6 +137,15 @@ function updateRegistryMenuItem(item: MenuItem, ctx: RouteContext): MenuItem { } return { ...subItem, visible: false, expanded: false }; } + + if (subItem.id === 'registries-moderation') { + return { + ...subItem, + visible: ctx.registrationModerationPageVisible, + routerLink: ['/registries', ctx.providerId, 'moderation'], + }; + } + return subItem; }); @@ -169,3 +178,20 @@ function updatePreprintMenuItem(item: MenuItem, ctx: RouteContext): MenuItem { return { ...item, expanded: ctx.isPreprint, items }; } + +function updateCollectionMenuItem(item: MenuItem, ctx: RouteContext): MenuItem { + const isCollections = ctx.isCollections; + + const items = (item.items || []).map((subItem) => { + if (subItem.id === 'collections-moderation') { + return { + ...subItem, + visible: isCollections && ctx.collectionModerationPageVisible, + routerLink: ['/collections', ctx.providerId, 'moderation'], + }; + } + return subItem; + }); + + return { ...item, items, visible: ctx.isCollections }; +} diff --git a/src/app/core/models/route-context.model.ts b/src/app/core/models/route-context.model.ts index 636bcf291..f87b3e82a 100644 --- a/src/app/core/models/route-context.model.ts +++ b/src/app/core/models/route-context.model.ts @@ -5,6 +5,8 @@ export interface RouteContext { isRegistry: boolean; isPreprint: boolean; preprintReviewsPageVisible?: boolean; + registrationModerationPageVisible?: boolean; + collectionModerationPageVisible?: boolean; isCollections: boolean; currentUrl?: string; isViewOnly?: boolean; diff --git a/src/app/core/store/provider/provider.actions.ts b/src/app/core/store/provider/provider.actions.ts index 698ca905b..aeed386b5 100644 --- a/src/app/core/store/provider/provider.actions.ts +++ b/src/app/core/store/provider/provider.actions.ts @@ -1,6 +1,10 @@ -import { ProviderModel } from '@osf/shared/models'; +import { ProviderShortInfoModel } from '@osf/shared/models'; export class SetCurrentProvider { static readonly type = '[Provider] Set Current Provider'; - constructor(public provider: ProviderModel) {} + constructor(public provider: ProviderShortInfoModel) {} +} + +export class ClearCurrentProvider { + static readonly type = '[Provider] Clear Current Provider'; } diff --git a/src/app/core/store/provider/provider.model.ts b/src/app/core/store/provider/provider.model.ts index 61d743858..bc5d90faa 100644 --- a/src/app/core/store/provider/provider.model.ts +++ b/src/app/core/store/provider/provider.model.ts @@ -1,7 +1,7 @@ -import { ProviderModel } from '@osf/shared/models'; +import { ProviderShortInfoModel } from '@osf/shared/models'; export interface ProviderStateModel { - currentProvider: ProviderModel | null; + currentProvider: ProviderShortInfoModel | null; } export const PROVIDER_STATE_INITIAL: ProviderStateModel = { diff --git a/src/app/core/store/provider/provider.selectors.ts b/src/app/core/store/provider/provider.selectors.ts index 9379ae8dc..c63f3b537 100644 --- a/src/app/core/store/provider/provider.selectors.ts +++ b/src/app/core/store/provider/provider.selectors.ts @@ -1,13 +1,13 @@ import { Selector } from '@ngxs/store'; -import { ProviderModel } from '@osf/shared/models'; +import { ProviderShortInfoModel } from '@osf/shared/models'; import { ProviderStateModel } from './provider.model'; import { ProviderState } from './provider.state'; export class ProviderSelectors { @Selector([ProviderState]) - static getCurrentProvider(state: ProviderStateModel): ProviderModel | null { + static getCurrentProvider(state: ProviderStateModel): ProviderShortInfoModel | null { return state.currentProvider; } } diff --git a/src/app/core/store/provider/provider.state.ts b/src/app/core/store/provider/provider.state.ts index 10cc0d83a..d0e62c67f 100644 --- a/src/app/core/store/provider/provider.state.ts +++ b/src/app/core/store/provider/provider.state.ts @@ -2,7 +2,7 @@ import { Action, State, StateContext } from '@ngxs/store'; import { Injectable } from '@angular/core'; -import { SetCurrentProvider } from './provider.actions'; +import { ClearCurrentProvider, SetCurrentProvider } from './provider.actions'; import { PROVIDER_STATE_INITIAL, ProviderStateModel } from './provider.model'; @State({ @@ -13,8 +13,11 @@ import { PROVIDER_STATE_INITIAL, ProviderStateModel } from './provider.model'; export class ProviderState { @Action(SetCurrentProvider) setCurrentProvider(ctx: StateContext, action: SetCurrentProvider) { - ctx.patchState({ - currentProvider: action.provider, - }); + ctx.patchState({ currentProvider: action.provider }); + } + + @Action(ClearCurrentProvider) + clearCurrentProvider(ctx: StateContext) { + ctx.setState(PROVIDER_STATE_INITIAL); } } diff --git a/src/app/core/store/user/user.actions.ts b/src/app/core/store/user/user.actions.ts index 591903ade..b76a8b834 100644 --- a/src/app/core/store/user/user.actions.ts +++ b/src/app/core/store/user/user.actions.ts @@ -45,10 +45,6 @@ export class UpdateProfileSettingsUser { constructor(public payload: Partial) {} } -export class SetUserAsModerator { - static readonly type = '[User] Set User As Moderator'; -} - export class AcceptTermsOfServiceByUser { static readonly type = '[User] Accept Terms Of Service'; } diff --git a/src/app/core/store/user/user.selectors.ts b/src/app/core/store/user/user.selectors.ts index 88ab9dd9d..5aad4c852 100644 --- a/src/app/core/store/user/user.selectors.ts +++ b/src/app/core/store/user/user.selectors.ts @@ -58,11 +58,6 @@ export class UserSelectors { return state.currentUser.data?.social; } - @Selector([UserState]) - static isCurrentUserModerator(state: UserStateModel): boolean { - return !!state.currentUser.data?.isModerator; - } - @Selector([UserState]) static getCanViewReviews(state: UserStateModel): boolean { return state.currentUser.data?.canViewReviews || false; diff --git a/src/app/core/store/user/user.state.ts b/src/app/core/store/user/user.state.ts index 58e2dfd72..8404141ec 100644 --- a/src/app/core/store/user/user.state.ts +++ b/src/app/core/store/user/user.state.ts @@ -18,7 +18,6 @@ import { GetCurrentUser, GetCurrentUserSettings, SetCurrentUser, - SetUserAsModerator, UpdateProfileSettingsEducation, UpdateProfileSettingsEmployment, UpdateProfileSettingsSocialLinks, @@ -234,26 +233,6 @@ export class UserState { ); } - @Action(SetUserAsModerator) - setUserAsModerator(ctx: StateContext) { - const state = ctx.getState(); - const currentUser = state.currentUser.data; - - if (!currentUser) { - return; - } - - ctx.patchState({ - currentUser: { - ...state.currentUser, - data: { - ...currentUser, - isModerator: true, - }, - }, - }); - } - @Action(AcceptTermsOfServiceByUser) acceptTermsOfServiceByUser(ctx: StateContext) { const state = ctx.getState(); diff --git a/src/app/features/admin-institutions/pages/institutions-projects/institutions-projects.component.ts b/src/app/features/admin-institutions/pages/institutions-projects/institutions-projects.component.ts index 36939b6f5..166109926 100644 --- a/src/app/features/admin-institutions/pages/institutions-projects/institutions-projects.component.ts +++ b/src/app/features/admin-institutions/pages/institutions-projects/institutions-projects.component.ts @@ -167,7 +167,7 @@ export class InstitutionsProjectsComponent implements OnInit, OnDestroy { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => this.toastService.showSuccess('adminInstitutions.institutionUsers.messageSent')); } else { - const projectId = (userRowData['title'] as TableCellLink).url.split('/').pop() || ''; + const projectId = (userRowData['link'] as TableCellLink).url.split('/').pop() || ''; this.actions .requestProjectAccess({ diff --git a/src/app/features/collections/collections.routes.ts b/src/app/features/collections/collections.routes.ts index 7f6b05c54..cd585fb10 100644 --- a/src/app/features/collections/collections.routes.ts +++ b/src/app/features/collections/collections.routes.ts @@ -6,7 +6,14 @@ import { authGuard } from '@osf/core/guards'; import { AddToCollectionState } from '@osf/features/collections/store/add-to-collection'; import { CollectionsModerationState } from '@osf/features/moderation/store/collections-moderation'; import { ConfirmLeavingGuard } from '@shared/guards'; -import { BookmarksState, CitationsState, ContributorsState, NodeLinksState, ProjectsState } from '@shared/stores'; +import { + BookmarksState, + CitationsState, + ContributorsState, + NodeLinksState, + ProjectsState, + SubjectsState, +} from '@shared/stores'; import { CollectionsState } from '@shared/stores/collections'; export const collectionsRoutes: Routes = [ @@ -58,7 +65,14 @@ export const collectionsRoutes: Routes = [ '@osf/features/moderation/components/collection-submission-overview/collection-submission-overview.component' ).then((mod) => mod.CollectionSubmissionOverviewComponent), providers: [ - provideStates([NodeLinksState, CitationsState, CollectionsModerationState, CollectionsState, BookmarksState]), + provideStates([ + NodeLinksState, + CitationsState, + CollectionsModerationState, + CollectionsState, + BookmarksState, + SubjectsState, + ]), ], }, ], diff --git a/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.ts b/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.ts index 6fe5c6b1e..6f20dbe3f 100644 --- a/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.ts +++ b/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.ts @@ -37,7 +37,7 @@ import { TagsInputComponent, TextInputComponent, TruncatedTextComponent } from ' import { InputLimits } from '@shared/constants'; import { ResourceType } from '@shared/enums'; import { LicenseModel } from '@shared/models'; -import { Project } from '@shared/models/projects'; +import { ProjectModel } from '@shared/models/projects'; import { InterpolatePipe } from '@shared/pipes'; import { ToastService } from '@shared/services'; import { ClearProjects, GetAllContributors, UpdateProjectMetadata } from '@shared/stores'; @@ -189,7 +189,7 @@ export class ProjectMetadataStepComponent { this.metadataSaved.emit(); } - private updateProjectMetadata(selectedProject: Project): void { + private updateProjectMetadata(selectedProject: ProjectModel): void { const metadata = this.formService.buildMetadataPayload(this.projectMetadataForm, selectedProject); this.actions diff --git a/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.ts b/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.ts index f88889e4a..78d9953c3 100644 --- a/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.ts +++ b/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.ts @@ -10,7 +10,7 @@ import { ChangeDetectionStrategy, Component, computed, input, output, signal } f import { AddToCollectionSteps } from '@osf/features/collections/enums'; import { SetSelectedProject } from '@osf/shared/stores'; import { ProjectSelectorComponent } from '@shared/components'; -import { Project } from '@shared/models/projects'; +import { ProjectModel } from '@shared/models/projects'; import { CollectionsSelectors, GetUserCollectionSubmissions } from '@shared/stores/collections'; import { ProjectsSelectors } from '@shared/stores/projects/projects.selectors'; @@ -32,7 +32,7 @@ export class SelectProjectStepComponent { stepChange = output(); projectSelected = output(); - currentSelectedProject = signal(null); + currentSelectedProject = signal(null); excludedProjectIds = computed(() => { const submissions = this.currentUserSubmissions(); @@ -44,7 +44,7 @@ export class SelectProjectStepComponent { getUserCollectionSubmissions: GetUserCollectionSubmissions, }); - handleProjectChange(project: Project | null): void { + handleProjectChange(project: ProjectModel | null): void { if (project) { this.currentSelectedProject.set(project); this.actions.setSelectedProject(project); @@ -53,7 +53,7 @@ export class SelectProjectStepComponent { } } - handleProjectsLoaded(projects: Project[]): void { + handleProjectsLoaded(projects: ProjectModel[]): void { const collectionId = this.collectionId(); if (collectionId && projects.length) { const projectIds = projects.map((project) => project.id); diff --git a/src/app/features/collections/components/collections-discover/collections-discover.component.ts b/src/app/features/collections/components/collections-discover/collections-discover.component.ts index 363456ffe..1f02a6d42 100644 --- a/src/app/features/collections/components/collections-discover/collections-discover.component.ts +++ b/src/app/features/collections/components/collections-discover/collections-discover.component.ts @@ -12,12 +12,11 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl } from '@angular/forms'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; -import { CollectionsHelpDialogComponent, CollectionsMainContentComponent } from '@osf/features/collections/components'; -import { CollectionsQuerySyncService } from '@osf/features/collections/services'; -import { LoadingSpinnerComponent, SearchInputComponent } from '@shared/components'; -import { HeaderStyleHelper } from '@shared/helpers'; -import { CollectionsFilters } from '@shared/models'; -import { BrandService } from '@shared/services'; +import { ClearCurrentProvider } from '@core/store/provider'; +import { LoadingSpinnerComponent, SearchInputComponent } from '@osf/shared/components'; +import { HeaderStyleHelper } from '@osf/shared/helpers'; +import { CollectionsFilters } from '@osf/shared/models'; +import { BrandService } from '@osf/shared/services'; import { ClearCollections, ClearCollectionSubmissions, @@ -27,7 +26,11 @@ import { SearchCollectionSubmissions, SetPageNumber, SetSearchValue, -} from '@shared/stores/collections'; +} from '@osf/shared/stores'; + +import { CollectionsQuerySyncService } from '../../services'; +import { CollectionsHelpDialogComponent } from '../collections-help-dialog/collections-help-dialog.component'; +import { CollectionsMainContentComponent } from '../collections-main-content'; @Component({ selector: 'osf-collections-discover', @@ -72,6 +75,7 @@ export class CollectionsDiscoverComponent { setPageNumber: SetPageNumber, clearCollections: ClearCollections, clearCollectionsSubmissions: ClearCollectionSubmissions, + clearCurrentProvider: ClearCurrentProvider, }); constructor() { diff --git a/src/app/features/collections/components/collections-filter-chips/collections-filter-chips.component.ts b/src/app/features/collections/components/collections-filter-chips/collections-filter-chips.component.ts index 77f067bf1..67a36c4ff 100644 --- a/src/app/features/collections/components/collections-filter-chips/collections-filter-chips.component.ts +++ b/src/app/features/collections/components/collections-filter-chips/collections-filter-chips.component.ts @@ -4,8 +4,6 @@ import { Chip } from 'primeng/chip'; import { ChangeDetectionStrategy, Component, computed } from '@angular/core'; -import { collectionFilterTypes } from '@osf/features/collections/constants/filter-types.const'; -import { CollectionFilterType } from '@osf/features/collections/enums'; import { CollectionsSelectors, SetCollectedTypeFilters, @@ -20,6 +18,9 @@ import { SetVolumeFilters, } from '@shared/stores/collections'; +import { collectionFilterTypes } from '../../constants'; +import { CollectionFilterType } from '../../enums'; + @Component({ selector: 'osf-collections-filter-chips', imports: [Chip], diff --git a/src/app/features/collections/components/collections-filters/collections-filters.component.scss b/src/app/features/collections/components/collections-filters/collections-filters.component.scss index 87860ca0c..bd2451693 100644 --- a/src/app/features/collections/components/collections-filters/collections-filters.component.scss +++ b/src/app/features/collections/components/collections-filters/collections-filters.component.scss @@ -1,8 +1,7 @@ -@use "styles/variables" as var; @use "styles/mixins" as mix; .filters { - border: 1px solid var.$grey-2; + border: 1px solid var(--grey-2); border-radius: mix.rem(12px); padding: 0 mix.rem(20px); display: flex; diff --git a/src/app/features/collections/components/collections-filters/collections-filters.component.ts b/src/app/features/collections/components/collections-filters/collections-filters.component.ts index cd560197c..683830251 100644 --- a/src/app/features/collections/components/collections-filters/collections-filters.component.ts +++ b/src/app/features/collections/components/collections-filters/collections-filters.component.ts @@ -8,8 +8,6 @@ import { MultiSelect, MultiSelectChangeEvent } from 'primeng/multiselect'; import { ChangeDetectionStrategy, Component, computed } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { collectionFilterTypes } from '@osf/features/collections/constants/filter-types.const'; -import { CollectionFilterType } from '@osf/features/collections/enums'; import { CollectionsSelectors, SetCollectedTypeFilters, @@ -24,6 +22,9 @@ import { SetVolumeFilters, } from '@shared/stores/collections'; +import { collectionFilterTypes } from '../../constants'; +import { CollectionFilterType } from '../../enums'; + @Component({ selector: 'osf-collections-filters', imports: [FormsModule, MultiSelect, Accordion, AccordionContent, AccordionHeader, AccordionPanel, TranslatePipe], diff --git a/src/app/features/collections/components/collections-help-dialog/collections-help-dialog.component.html b/src/app/features/collections/components/collections-help-dialog/collections-help-dialog.component.html index 4cd774891..fad0705a6 100644 --- a/src/app/features/collections/components/collections-help-dialog/collections-help-dialog.component.html +++ b/src/app/features/collections/components/collections-help-dialog/collections-help-dialog.component.html @@ -1,8 +1,8 @@ diff --git a/src/app/features/collections/components/collections-help-dialog/collections-help-dialog.component.scss b/src/app/features/collections/components/collections-help-dialog/collections-help-dialog.component.scss index c394c1464..a439c928e 100644 --- a/src/app/features/collections/components/collections-help-dialog/collections-help-dialog.component.scss +++ b/src/app/features/collections/components/collections-help-dialog/collections-help-dialog.component.scss @@ -1,7 +1,5 @@ -@use "styles/variables" as var; - .dialog-content { - border-top: 1px solid var.$grey-2; - border-bottom: 1px solid var.$grey-2; + border-top: 1px solid var(--grey-2); + border-bottom: 1px solid var(--grey-2); line-height: 2; } diff --git a/src/app/features/collections/components/collections-main-content/collections-main-content.component.scss b/src/app/features/collections/components/collections-main-content/collections-main-content.component.scss index ba7865aa7..7a7ac0cb2 100644 --- a/src/app/features/collections/components/collections-main-content/collections-main-content.component.scss +++ b/src/app/features/collections/components/collections-main-content/collections-main-content.component.scss @@ -1,16 +1,15 @@ -@use "styles/variables" as var; @use "styles/mixins" as mix; .sort-card { @include mix.flex-center; width: 100%; height: mix.rem(48px); - border: 1px solid var.$grey-2; + border: 1px solid var(--grey-2); border-radius: mix.rem(12px); padding: 0 mix.rem(28px); cursor: pointer; } .card-selected { - background: var.$bg-blue-2; + background: var(--bg-blue-2); } diff --git a/src/app/features/collections/components/collections-main-content/collections-main-content.component.ts b/src/app/features/collections/components/collections-main-content/collections-main-content.component.ts index 700cc6b69..3f01aa829 100644 --- a/src/app/features/collections/components/collections-main-content/collections-main-content.component.ts +++ b/src/app/features/collections/components/collections-main-content/collections-main-content.component.ts @@ -48,22 +48,16 @@ export class CollectionsMainContentComponent { isCollectionProviderLoading = select(CollectionsSelectors.getCollectionProviderLoading); isCollectionDetailsLoading = select(CollectionsSelectors.getCollectionDetailsLoading); - isCollectionLoading = computed(() => { - return this.isCollectionProviderLoading() || this.isCollectionDetailsLoading(); - }); + isCollectionLoading = computed(() => this.isCollectionProviderLoading() || this.isCollectionDetailsLoading()); hasAnySelectedFilters = computed(() => { const currentFilters = this.selectedFilters(); - const hasSelectedFiltersOptions = Object.values(currentFilters).some((value) => { - return value.length; - }); + const hasSelectedFiltersOptions = Object.values(currentFilters).some((value) => value.length); return hasSelectedFiltersOptions; }); - actions = createDispatchMap({ - setSortBy: SetSortBy, - }); + actions = createDispatchMap({ setSortBy: SetSortBy }); openFilters(): void { this.isFiltersOpen.set(!this.isFiltersOpen()); diff --git a/src/app/features/collections/components/collections-search-results/collections-search-results.component.ts b/src/app/features/collections/components/collections-search-results/collections-search-results.component.ts index c456fb6af..db710ea7d 100644 --- a/src/app/features/collections/components/collections-search-results/collections-search-results.component.ts +++ b/src/app/features/collections/components/collections-search-results/collections-search-results.component.ts @@ -27,13 +27,9 @@ export class CollectionsSearchResultsComponent { totalSubmissions = select(CollectionsSelectors.getTotalSubmissions); pageNumber = select(CollectionsSelectors.getPageNumber); - actions = createDispatchMap({ - setPageNumber: SetPageNumber, - }); + actions = createDispatchMap({ setPageNumber: SetPageNumber }); - isLoading = computed(() => { - return this.isCollectionDetailsLoading() || this.isCollectionSubmissionsLoading(); - }); + isLoading = computed(() => this.isCollectionDetailsLoading() || this.isCollectionSubmissionsLoading()); firstIndex = computed(() => (parseInt(this.pageNumber()) - 1) * 10); diff --git a/src/app/features/collections/constants/filter-types.const.ts b/src/app/features/collections/constants/filter-types.const.ts index 6645f4a75..0cfc6f232 100644 --- a/src/app/features/collections/constants/filter-types.const.ts +++ b/src/app/features/collections/constants/filter-types.const.ts @@ -1,4 +1,4 @@ -import { CollectionFilterType } from '@osf/features/collections/enums'; +import { CollectionFilterType } from '../enums'; export const collectionFilterTypes: CollectionFilterType[] = [ CollectionFilterType.ProgramArea, diff --git a/src/app/features/collections/constants/index.ts b/src/app/features/collections/constants/index.ts index f1e8192b6..5e7661ebb 100644 --- a/src/app/features/collections/constants/index.ts +++ b/src/app/features/collections/constants/index.ts @@ -1,2 +1,4 @@ export * from './filter-names.const'; +export * from './filter-types.const'; +export * from './query-params-keys.const'; export * from './sort-options.const'; diff --git a/src/app/features/collections/services/project-metadata-form.service.ts b/src/app/features/collections/services/project-metadata-form.service.ts index 75fb6e2cf..5c518b105 100644 --- a/src/app/features/collections/services/project-metadata-form.service.ts +++ b/src/app/features/collections/services/project-metadata-form.service.ts @@ -5,7 +5,7 @@ import { ProjectMetadataFormControls } from '@osf/features/collections/enums'; import { ProjectMetadataForm } from '@osf/features/collections/models'; import { CustomValidators } from '@osf/shared/helpers'; import { LicenseModel, ProjectMetadataUpdatePayload } from '@shared/models'; -import { Project } from '@shared/models/projects'; +import { ProjectModel } from '@shared/models/projects'; @Injectable({ providedIn: 'root', @@ -55,7 +55,7 @@ export class ProjectMetadataFormService { populateFormFromProject( form: FormGroup, - project: Project, + project: ProjectModel, license: LicenseModel | null ): { tags: string[] } { const tags = project.tags || []; @@ -73,7 +73,7 @@ export class ProjectMetadataFormService { return { tags }; } - patchLicenseData(form: FormGroup, license: LicenseModel, project: Project): void { + patchLicenseData(form: FormGroup, license: LicenseModel, project: ProjectModel): void { form.patchValue({ [ProjectMetadataFormControls.License]: license, [ProjectMetadataFormControls.LicenseYear]: @@ -87,7 +87,7 @@ export class ProjectMetadataFormService { form.get(ProjectMetadataFormControls.Tags)?.markAsTouched(); } - buildMetadataPayload(form: FormGroup, project: Project): ProjectMetadataUpdatePayload { + buildMetadataPayload(form: FormGroup, project: ProjectModel): ProjectMetadataUpdatePayload { const formValue = form.value; return { diff --git a/src/app/features/home/pages/dashboard/dashboard.component.html b/src/app/features/home/pages/dashboard/dashboard.component.html index 40194e726..24d95d494 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.html +++ b/src/app/features/home/pages/dashboard/dashboard.component.html @@ -77,9 +77,7 @@

{{ 'home.loggedIn.hosting.title' | translate }}

[buttonLabel]="'home.loggedIn.dashboard.createProject' | translate" (buttonClick)="createProject()" /> - <<<<<<< HEAD - ======= >>>>>>> origin/develop

{{ 'home.loggedIn.dashboard.noCreatedProject' | translate }}

diff --git a/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts b/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts index c4e9cc827..7517fc8e4 100644 --- a/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts +++ b/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts @@ -37,8 +37,8 @@ import { DeleteContributor, UpdateBibliographyFilter, UpdateContributor, + UpdateContributorsSearchValue, UpdatePermissionFilter, - UpdateSearchValue, } from '@osf/shared/stores'; @Component({ @@ -70,13 +70,14 @@ export class ContributorsDialogComponent implements OnInit { const initialContributors = this.initialContributors(); if (!currentUserId) return false; - return initialContributors.some((contributor: ContributorModel) => { - return contributor.userId === currentUserId && contributor.permission === ContributorPermission.Admin; - }); + return initialContributors.some( + (contributor: ContributorModel) => + contributor.userId === currentUserId && contributor.permission === ContributorPermission.Admin + ); }); actions = createDispatchMap({ - updateSearchValue: UpdateSearchValue, + updateSearchValue: UpdateContributorsSearchValue, updatePermissionFilter: UpdatePermissionFilter, updateBibliographyFilter: UpdateBibliographyFilter, deleteContributor: DeleteContributor, diff --git a/src/app/features/moderation/collection-moderation.routes.ts b/src/app/features/moderation/collection-moderation.routes.ts index 9264f9c16..421f1cee5 100644 --- a/src/app/features/moderation/collection-moderation.routes.ts +++ b/src/app/features/moderation/collection-moderation.routes.ts @@ -17,7 +17,7 @@ export const collectionModerationRoutes: Routes = [ import('@osf/features/moderation/pages/collection-moderation/collection-moderation.component').then( (m) => m.CollectionModerationComponent ), - providers: [provideStates([ActivityLogsState])], + providers: [provideStates([ActivityLogsState, CollectionsState])], children: [ { path: '', @@ -31,7 +31,7 @@ export const collectionModerationRoutes: Routes = [ (m) => m.CollectionModerationSubmissionsComponent ), data: { tab: CollectionModerationTab.AllItems }, - providers: [provideStates([CollectionsModerationState, CollectionsState])], + providers: [provideStates([CollectionsModerationState])], }, { path: 'moderators', diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts index 066f90cc8..867e22cda 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts @@ -22,7 +22,6 @@ import { ClearCollectionSubmissions, CollectionsSelectors, GetCollectionDetails, - GetCollectionProvider, SearchCollectionSubmissions, SetPageNumber, } from '@osf/shared/stores'; @@ -62,15 +61,12 @@ export class CollectionModerationSubmissionsComponent { isSubmissionsLoading = select(CollectionsModerationSelectors.getCollectionSubmissionsLoading); collectionSubmissions = select(CollectionsModerationSelectors.getCollectionSubmissions); totalSubmissions = select(CollectionsModerationSelectors.getCollectionSubmissionsTotalCount); - providerId = signal(''); primaryCollectionId = computed(() => this.collectionProvider()?.primaryCollection?.id); reviewStatus = signal(SubmissionReviewStatus.Pending); currentPage = signal('1'); pageSize = 10; - isLoading = computed(() => { - return this.isCollectionProviderLoading() || this.isSubmissionsLoading(); - }); + isLoading = computed(() => this.isCollectionProviderLoading() || this.isSubmissionsLoading()); sortOptions = COLLECTION_SUBMISSIONS_SORT_OPTIONS; selectedSortOption = signal(this.sortOptions[0].value); @@ -78,7 +74,6 @@ export class CollectionModerationSubmissionsComponent { firstIndex = computed(() => (parseInt(this.currentPage()) - 1) * 10); actions = createDispatchMap({ - getCollectionProvider: GetCollectionProvider, getCollectionDetails: GetCollectionDetails, searchCollectionSubmissions: SearchCollectionSubmissions, getCollectionSubmissions: GetCollectionSubmissions, @@ -129,7 +124,6 @@ export class CollectionModerationSubmissionsComponent { constructor() { this.initializeFromQueryParams(); - this.initializeCollectionProvider(); this.setupEffects(); } @@ -193,16 +187,4 @@ export class CollectionModerationSubmissionsComponent { queryParamsHandling: 'merge', }); } - - private initializeCollectionProvider(): void { - const id = this.route.parent?.snapshot.paramMap.get('providerId'); - - if (!id) { - this.router.navigate(['/not-found']); - return; - } - - this.providerId.set(id); - this.actions.getCollectionProvider(id); - } } diff --git a/src/app/features/moderation/components/moderators-list/moderators-list.component.ts b/src/app/features/moderation/components/moderators-list/moderators-list.component.ts index 95bf596be..9e963da8c 100644 --- a/src/app/features/moderation/components/moderators-list/moderators-list.component.ts +++ b/src/app/features/moderation/components/moderators-list/moderators-list.component.ts @@ -26,7 +26,6 @@ import { UserSelectors } from '@core/store/user'; import { SearchInputComponent } from '@osf/shared/components'; import { ResourceType } from '@osf/shared/enums'; import { CustomConfirmationService, ToastService } from '@osf/shared/services'; -import { UpdateSearchValue } from '@osf/shared/stores'; import { AddModeratorType, ModeratorPermission } from '../../enums'; import { ModeratorDialogAddModel, ModeratorModel } from '../../models'; @@ -36,6 +35,7 @@ import { LoadModerators, ModeratorsSelectors, UpdateModerator, + UpdateModeratorsSearchValue, } from '../../store/moderators'; import { AddModeratorDialogComponent } from '../add-moderator-dialog/add-moderator-dialog.component'; import { InviteModeratorDialogComponent } from '../invite-moderator-dialog/invite-moderator-dialog.component'; @@ -83,7 +83,7 @@ export class ModeratorsListComponent implements OnInit { actions = createDispatchMap({ loadModerators: LoadModerators, - updateSearchValue: UpdateSearchValue, + updateSearchValue: UpdateModeratorsSearchValue, addModerators: AddModerator, updateModerator: UpdateModerator, deleteModerator: DeleteModerator, diff --git a/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.spec.ts b/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.spec.ts index 8015bb200..ed46afac6 100644 --- a/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.spec.ts +++ b/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.spec.ts @@ -10,6 +10,9 @@ import { TranslateServiceMock } from '@shared/mocks'; import { CollectionModerationComponent } from './collection-moderation.component'; +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; + describe('CollectionModerationComponent', () => { let component: CollectionModerationComponent; let fixture: ComponentFixture; @@ -22,6 +25,7 @@ describe('CollectionModerationComponent', () => { tab: null, }, }, + params: { providerId: 'osf' }, }, }; @@ -33,11 +37,12 @@ describe('CollectionModerationComponent', () => { isMediumSubject = new BehaviorSubject(true); await TestBed.configureTestingModule({ - imports: [CollectionModerationComponent], + imports: [CollectionModerationComponent, OSFTestingModule], providers: [ { provide: ActivatedRoute, useValue: mockActivatedRoute }, { provide: Router, useValue: mockRouter }, MockProvider(IS_MEDIUM, isMediumSubject), + provideMockStore(), TranslateServiceMock, ], }).compileComponents(); diff --git a/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.ts b/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.ts index b847526fc..a6ea58f3d 100644 --- a/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.ts +++ b/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.ts @@ -1,14 +1,18 @@ +import { createDispatchMap } from '@ngxs/store'; + import { TranslatePipe } from '@ngx-translate/core'; import { Tab, TabList, TabPanels, Tabs } from 'primeng/tabs'; -import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; +import { ClearCurrentProvider } from '@core/store/provider'; import { SelectComponent, SubHeaderComponent } from '@osf/shared/components'; import { IS_MEDIUM, Primitive } from '@osf/shared/helpers'; +import { GetCollectionProvider } from '@osf/shared/stores'; import { COLLECTION_MODERATION_TABS } from '../../constants'; import { CollectionModerationTab } from '../../enums'; @@ -30,7 +34,7 @@ import { CollectionModerationTab } from '../../enums'; styleUrl: './collection-moderation.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class CollectionModerationComponent implements OnInit { +export class CollectionModerationComponent implements OnInit, OnDestroy { readonly route = inject(ActivatedRoute); readonly router = inject(Router); @@ -39,8 +43,25 @@ export class CollectionModerationComponent implements OnInit { selectedTab = CollectionModerationTab.AllItems; + actions = createDispatchMap({ + getCollectionProvider: GetCollectionProvider, + clearCurrentProvider: ClearCurrentProvider, + }); + ngOnInit(): void { this.selectedTab = this.route.snapshot.firstChild?.data['tab']; + const id = this.route.snapshot.params['providerId']; + + if (!id) { + this.router.navigate(['/not-found']); + return; + } + + this.actions.getCollectionProvider(id); + } + + ngOnDestroy(): void { + this.actions.clearCurrentProvider(); } onTabChange(value: Primitive): void { diff --git a/src/app/features/moderation/pages/registries-moderation/registries-moderation.component.spec.ts b/src/app/features/moderation/pages/registries-moderation/registries-moderation.component.spec.ts index bba2c1116..04aeb7c8b 100644 --- a/src/app/features/moderation/pages/registries-moderation/registries-moderation.component.spec.ts +++ b/src/app/features/moderation/pages/registries-moderation/registries-moderation.component.spec.ts @@ -10,6 +10,9 @@ import { TranslateServiceMock } from '@shared/mocks'; import { RegistriesModerationComponent } from './registries-moderation.component'; +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; + describe('RegistriesModerationComponent', () => { let component: RegistriesModerationComponent; let fixture: ComponentFixture; @@ -22,6 +25,7 @@ describe('RegistriesModerationComponent', () => { tab: null, }, }, + params: { providerId: 'osf' }, }, }; @@ -33,11 +37,12 @@ describe('RegistriesModerationComponent', () => { isMediumSubject = new BehaviorSubject(true); await TestBed.configureTestingModule({ - imports: [RegistriesModerationComponent], + imports: [RegistriesModerationComponent, OSFTestingModule], providers: [ { provide: ActivatedRoute, useValue: mockActivatedRoute }, MockProvider(Router, mockRouter), MockProvider(IS_MEDIUM, isMediumSubject), + provideMockStore(), TranslateServiceMock, ], }).compileComponents(); diff --git a/src/app/features/moderation/pages/registries-moderation/registries-moderation.component.ts b/src/app/features/moderation/pages/registries-moderation/registries-moderation.component.ts index 991ddb182..aec8cd656 100644 --- a/src/app/features/moderation/pages/registries-moderation/registries-moderation.component.ts +++ b/src/app/features/moderation/pages/registries-moderation/registries-moderation.component.ts @@ -1,15 +1,19 @@ +import { createDispatchMap } from '@ngxs/store'; + import { TranslatePipe } from '@ngx-translate/core'; import { Tab, TabList, TabPanels, Tabs } from 'primeng/tabs'; -import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; +import { ClearCurrentProvider } from '@core/store/provider'; import { SelectComponent, SubHeaderComponent } from '@osf/shared/components'; import { ResourceType } from '@osf/shared/enums'; import { IS_MEDIUM, Primitive } from '@osf/shared/helpers'; +import { GetRegistryProviderBrand } from '@osf/shared/stores/registration-provider'; import { REGISTRY_MODERATION_TABS } from '../../constants'; import { RegistryModerationTab } from '../../enums'; @@ -31,7 +35,7 @@ import { RegistryModerationTab } from '../../enums'; styleUrl: './registries-moderation.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class RegistriesModerationComponent implements OnInit { +export class RegistriesModerationComponent implements OnInit, OnDestroy { readonly resourceType = ResourceType.Registration; readonly route = inject(ActivatedRoute); readonly router = inject(Router); @@ -39,10 +43,27 @@ export class RegistriesModerationComponent implements OnInit { readonly tabOptions = REGISTRY_MODERATION_TABS; readonly isMedium = toSignal(inject(IS_MEDIUM)); + actions = createDispatchMap({ + getProvider: GetRegistryProviderBrand, + clearCurrentProvider: ClearCurrentProvider, + }); + selectedTab = RegistryModerationTab.Submitted; ngOnInit(): void { - this.selectedTab = this.route.snapshot.firstChild?.data['tab'] as RegistryModerationTab; + this.selectedTab = this.route.snapshot.firstChild?.data['tab']; + const id = this.route.snapshot.params['providerId']; + + if (!id) { + this.router.navigate(['/not-found']); + return; + } + + this.actions.getProvider(id); + } + + ngOnDestroy(): void { + this.actions.clearCurrentProvider(); } onTabChange(value: Primitive): void { diff --git a/src/app/features/moderation/services/moderators.service.ts b/src/app/features/moderation/services/moderators.service.ts index df6558f2f..ee9a78dc8 100644 --- a/src/app/features/moderation/services/moderators.service.ts +++ b/src/app/features/moderation/services/moderators.service.ts @@ -26,7 +26,7 @@ export class ModeratorsService { ]); getModerators(resourceId: string, resourceType: ResourceType): Observable { - const baseUrl = `${this.apiUrl}/${this.urlMap.get(resourceType)}/${resourceId}/moderators`; + const baseUrl = `${this.apiUrl}/${this.urlMap.get(resourceType)}/${resourceId}/moderators/`; return this.jsonApiService .get(baseUrl) diff --git a/src/app/features/moderation/store/moderators/moderators.actions.ts b/src/app/features/moderation/store/moderators/moderators.actions.ts index fd98872bf..25f3186ae 100644 --- a/src/app/features/moderation/store/moderators/moderators.actions.ts +++ b/src/app/features/moderation/store/moderators/moderators.actions.ts @@ -43,7 +43,7 @@ export class DeleteModerator { ) {} } -export class UpdateSearchValue { +export class UpdateModeratorsSearchValue { static readonly type = `${ACTION_SCOPE} Update Search Value`; constructor(public searchValue: string | null) {} diff --git a/src/app/features/moderation/store/moderators/moderators.state.ts b/src/app/features/moderation/store/moderators/moderators.state.ts index 9db6dd46f..9733135af 100644 --- a/src/app/features/moderation/store/moderators/moderators.state.ts +++ b/src/app/features/moderation/store/moderators/moderators.state.ts @@ -16,7 +16,7 @@ import { LoadModerators, SearchUsers, UpdateModerator, - UpdateSearchValue, + UpdateModeratorsSearchValue, } from './moderators.actions'; import { MODERATORS_STATE_DEFAULTS, ModeratorsStateModel } from './moderators.model'; @@ -54,8 +54,8 @@ export class ModeratorsState { ); } - @Action(UpdateSearchValue) - updateSearchValue(ctx: StateContext, action: UpdateSearchValue) { + @Action(UpdateModeratorsSearchValue) + updateModeratorsSearchValue(ctx: StateContext, action: UpdateModeratorsSearchValue) { ctx.patchState({ moderators: { ...ctx.getState().moderators, searchValue: action.searchValue }, }); diff --git a/src/app/features/preprints/models/preprint-provider-json-api.models.ts b/src/app/features/preprints/models/preprint-provider-json-api.models.ts index 7e92396c2..c75f31f62 100644 --- a/src/app/features/preprints/models/preprint-provider-json-api.models.ts +++ b/src/app/features/preprints/models/preprint-provider-json-api.models.ts @@ -1,6 +1,6 @@ +import { ReviewPermissions } from '@osf/shared/enums'; import { StringOrNull } from '@osf/shared/helpers'; -import { ReviewPermissions } from '@shared/enums/review-permissions.enum'; -import { BrandDataJsonApi } from '@shared/models'; +import { BrandDataJsonApi } from '@osf/shared/models'; import { ProviderReviewsWorkflow } from '../enums'; diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts index c0a186b1e..9b3a8903d 100644 --- a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts @@ -22,6 +22,7 @@ import { import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router } from '@angular/router'; +import { ClearCurrentProvider } from '@core/store/provider'; import { UserSelectors } from '@core/store/user'; import { AdditionalInfoComponent, @@ -102,6 +103,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { fetchPreprintRequests: FetchPreprintRequests, fetchPreprintReviewActions: FetchPreprintReviewActions, fetchPreprintRequestActions: FetchPreprintRequestActions, + clearCurrentProvider: ClearCurrentProvider, }); currentUser = select(UserSelectors.getCurrentUser); preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); @@ -289,6 +291,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { ngOnDestroy() { this.actions.resetState(); + this.actions.clearCurrentProvider(); } fetchPreprintVersion(preprintVersionId: string) { diff --git a/src/app/features/preprints/store/preprint-providers/preprint-providers.state.ts b/src/app/features/preprints/store/preprint-providers/preprint-providers.state.ts index 3277d6af8..f7b9188e3 100644 --- a/src/app/features/preprints/store/preprint-providers/preprint-providers.state.ts +++ b/src/app/features/preprints/store/preprint-providers/preprint-providers.state.ts @@ -6,7 +6,9 @@ import { catchError } from 'rxjs/operators'; import { inject, Injectable } from '@angular/core'; +import { SetCurrentProvider } from '@core/store/provider'; import { PreprintProvidersService } from '@osf/features/preprints/services'; +import { CurrentResourceType } from '@osf/shared/enums'; import { handleSectionError } from '@osf/shared/helpers'; import { @@ -33,6 +35,14 @@ export class PreprintProvidersState { const shouldRefresh = this.shouldRefresh(cachedData?.lastFetched); if (cachedData && !shouldRefresh) { + ctx.dispatch( + new SetCurrentProvider({ + id: cachedData.id, + name: cachedData.name, + type: CurrentResourceType.Preprints, + permissions: cachedData.permissions, + }) + ); return of(cachedData); } @@ -53,6 +63,15 @@ export class PreprintProvidersState { }), }) ); + + ctx.dispatch( + new SetCurrentProvider({ + id: preprintProvider.id, + name: preprintProvider.name, + type: CurrentResourceType.Preprints, + permissions: preprintProvider.permissions, + }) + ); }), catchError((error) => handleSectionError(ctx, 'preprintProvidersDetails', error)) ); diff --git a/src/app/features/project/contributors/contributors.component.spec.ts b/src/app/features/project/contributors/contributors.component.spec.ts index d5764ab3e..081356027 100644 --- a/src/app/features/project/contributors/contributors.component.spec.ts +++ b/src/app/features/project/contributors/contributors.component.spec.ts @@ -150,7 +150,7 @@ describe('ContributorsComponent', () => { expect(component.hasChanges).toBe(false); const modifiedContributors = [...mockContributors]; - modifiedContributors[0].permission = 'write'; + modifiedContributors[0].permission = ContributorPermission.Write; (component.contributors as any).set(modifiedContributors); expect((component.contributors as any)()).toEqual(modifiedContributors); @@ -158,7 +158,7 @@ describe('ContributorsComponent', () => { it('should cancel changes', () => { const modifiedContributors = [...mockContributors]; - modifiedContributors[0].permission = 'write'; + modifiedContributors[0].permission = ContributorPermission.Write; (component.contributors as any).set(modifiedContributors); component.cancel(); @@ -170,7 +170,7 @@ describe('ContributorsComponent', () => { jest.spyOn(component.toastService, 'showSuccess'); const modifiedContributors = [...mockContributors]; - modifiedContributors[0].permission = 'write'; + modifiedContributors[0].permission = ContributorPermission.Write; (component.contributors as any).set(modifiedContributors); expect(() => component.save()).not.toThrow(); @@ -180,7 +180,7 @@ describe('ContributorsComponent', () => { jest.spyOn(component.toastService, 'showError'); const modifiedContributors = [...mockContributors]; - modifiedContributors[0].permission = 'write'; + modifiedContributors[0].permission = ContributorPermission.Write; (component.contributors as any).set(modifiedContributors); expect(() => component.save()).not.toThrow(); diff --git a/src/app/features/project/contributors/contributors.component.ts b/src/app/features/project/contributors/contributors.component.ts index 2e047fad3..196caf851 100644 --- a/src/app/features/project/contributors/contributors.component.ts +++ b/src/app/features/project/contributors/contributors.component.ts @@ -53,8 +53,8 @@ import { GetResourceDetails, UpdateBibliographyFilter, UpdateContributor, + UpdateContributorsSearchValue, UpdatePermissionFilter, - UpdateSearchValue, ViewOnlyLinkSelectors, } from '@osf/shared/stores'; @@ -124,7 +124,7 @@ export class ContributorsComponent implements OnInit { getViewOnlyLinks: FetchViewOnlyLinks, getResourceDetails: GetResourceDetails, getContributors: GetAllContributors, - updateSearchValue: UpdateSearchValue, + updateSearchValue: UpdateContributorsSearchValue, updatePermissionFilter: UpdatePermissionFilter, updateBibliographyFilter: UpdateBibliographyFilter, deleteContributor: DeleteContributor, diff --git a/src/app/features/project/overview/components/files-widget/files-widget.component.ts b/src/app/features/project/overview/components/files-widget/files-widget.component.ts index 5007345db..83116ee52 100644 --- a/src/app/features/project/overview/components/files-widget/files-widget.component.ts +++ b/src/app/features/project/overview/components/files-widget/files-widget.component.ts @@ -39,7 +39,7 @@ import { OsfFile, SelectOption, } from '@osf/shared/models'; -import { Project } from '@osf/shared/models/projects'; +import { ProjectModel } from '@osf/shared/models/projects'; import { environment } from 'src/environments/environment'; @@ -161,7 +161,7 @@ export class FilesWidgetComponent { } private flatComponents( - components: (Partial & { children?: Project[] })[] = [], + components: (Partial & { children?: ProjectModel[] })[] = [], parentPath = '..' ): SelectOption[] { return components.flatMap((component) => { diff --git a/src/app/features/registries/components/registry-provider-hero/registry-provider-hero.component.html b/src/app/features/registries/components/registry-provider-hero/registry-provider-hero.component.html index 9569eaa74..4efab8ac6 100644 --- a/src/app/features/registries/components/registry-provider-hero/registry-provider-hero.component.html +++ b/src/app/features/registries/components/registry-provider-hero/registry-provider-hero.component.html @@ -5,7 +5,7 @@ } @else { - Provider Logo + Provider Logo }
diff --git a/src/app/features/registries/components/registry-provider-hero/registry-provider-hero.component.ts b/src/app/features/registries/components/registry-provider-hero/registry-provider-hero.component.ts index beefe4e03..a5377306a 100644 --- a/src/app/features/registries/components/registry-provider-hero/registry-provider-hero.component.ts +++ b/src/app/features/registries/components/registry-provider-hero/registry-provider-hero.component.ts @@ -10,8 +10,8 @@ import { FormControl } from '@angular/forms'; import { Router } from '@angular/router'; import { PreprintsHelpDialogComponent } from '@osf/features/preprints/components'; -import { RegistryProviderDetails } from '@osf/features/registries/models/registry-provider.model'; import { HeaderStyleHelper } from '@osf/shared/helpers'; +import { RegistryProviderDetails } from '@osf/shared/models'; import { SearchInputComponent } from '@shared/components'; import { DecodeHtmlPipe } from '@shared/pipes'; import { BrandService } from '@shared/services'; @@ -42,7 +42,7 @@ export class RegistryProviderHeroComponent implements OnDestroy { effect(() => { const provider = this.provider(); - if (provider) { + if (provider?.brand) { BrandService.applyBranding(provider.brand); HeaderStyleHelper.applyHeaderStyles( this.WHITE, diff --git a/src/app/features/registries/components/select-components-dialog/select-components-dialog.component.ts b/src/app/features/registries/components/select-components-dialog/select-components-dialog.component.ts index ce8327310..25350b20b 100644 --- a/src/app/features/registries/components/select-components-dialog/select-components-dialog.component.ts +++ b/src/app/features/registries/components/select-components-dialog/select-components-dialog.component.ts @@ -7,7 +7,7 @@ import { Tree } from 'primeng/tree'; import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; -import { Project } from '../../models'; +import { ProjectShortInfoModel } from '../../models'; @Component({ selector: 'osf-select-components-dialog', @@ -20,7 +20,7 @@ export class SelectComponentsDialogComponent { readonly dialogRef = inject(DynamicDialogRef); readonly config = inject(DynamicDialogConfig); selectedComponents: TreeNode[] = []; - parent: Project = this.config.data.parent; + parent: ProjectShortInfoModel = this.config.data.parent; components: TreeNode[] = []; constructor() { @@ -37,7 +37,7 @@ export class SelectComponentsDialogComponent { this.selectedComponents.push({ key: this.parent.id }); } - private mapProjectToTreeNode = (project: Project): TreeNode => { + private mapProjectToTreeNode = (project: ProjectShortInfoModel): TreeNode => { this.selectedComponents.push({ key: project.id, }); diff --git a/src/app/features/registries/mappers/index.ts b/src/app/features/registries/mappers/index.ts index cea020f3c..8c8a64cf3 100644 --- a/src/app/features/registries/mappers/index.ts +++ b/src/app/features/registries/mappers/index.ts @@ -1,2 +1 @@ export * from './licenses.mapper'; -export * from './providers.mapper'; diff --git a/src/app/features/registries/mappers/providers.mapper.ts b/src/app/features/registries/mappers/providers.mapper.ts deleted file mode 100644 index 593984a46..000000000 --- a/src/app/features/registries/mappers/providers.mapper.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ProvidersResponseJsonApi } from '@osf/shared/models'; - -import { ProviderSchema, RegistryProviderDetails, RegistryProviderDetailsJsonApi } from '../models'; - -export class ProvidersMapper { - static fromProvidersResponse(response: ProvidersResponseJsonApi): ProviderSchema[] { - return response.data.map((item) => ({ - id: item.id, - name: item.attributes.name, - })); - } - - static fromRegistryProvider(response: RegistryProviderDetailsJsonApi): RegistryProviderDetails { - const brandRaw = response.embeds!.brand.data; - return { - id: response.id, - name: response.attributes.name, - descriptionHtml: response.attributes.description, - permissions: response.attributes.permissions, - brand: { - id: brandRaw.id, - name: brandRaw.attributes.name, - heroLogoImageUrl: brandRaw.attributes.hero_logo_image, - heroBackgroundImageUrl: brandRaw.attributes.hero_background_image, - topNavLogoImageUrl: brandRaw.attributes.topnav_logo_image, - primaryColor: brandRaw.attributes.primary_color, - secondaryColor: brandRaw.attributes.secondary_color, - backgroundColor: brandRaw.attributes.background_color, - }, - iri: response.links.iri, - }; - } -} diff --git a/src/app/features/registries/models/index.ts b/src/app/features/registries/models/index.ts index 3b045981b..101e1aeac 100644 --- a/src/app/features/registries/models/index.ts +++ b/src/app/features/registries/models/index.ts @@ -1,4 +1 @@ -export * from './project'; -export * from './provider-schema.model'; -export * from './registry-provider.model'; -export * from './registry-provider-json-api.model'; +export * from './project-short-info.model'; diff --git a/src/app/features/registries/models/project-short-info.model.ts b/src/app/features/registries/models/project-short-info.model.ts new file mode 100644 index 000000000..86e569c39 --- /dev/null +++ b/src/app/features/registries/models/project-short-info.model.ts @@ -0,0 +1,5 @@ +export interface ProjectShortInfoModel { + id: string; + title: string; + children?: ProjectShortInfoModel[]; +} diff --git a/src/app/features/registries/models/project.ts b/src/app/features/registries/models/project.ts deleted file mode 100644 index dc1ae9d02..000000000 --- a/src/app/features/registries/models/project.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface Project { - id: string; - title: string; - children?: Project[]; -} diff --git a/src/app/features/registries/models/registry-provider-json-api.model.ts b/src/app/features/registries/models/registry-provider-json-api.model.ts deleted file mode 100644 index e0e451f9d..000000000 --- a/src/app/features/registries/models/registry-provider-json-api.model.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { BrandDataJsonApi, RegistrationProviderAttributesJsonApi } from '@shared/models'; - -export interface RegistryProviderDetailsJsonApi { - id: string; - type: 'registration-providers'; - attributes: RegistrationProviderAttributesJsonApi; - embeds?: { - brand: { - data: BrandDataJsonApi; - }; - }; - links: { - iri: string; - }; -} diff --git a/src/app/features/registries/pages/registries-landing/registries-landing.component.ts b/src/app/features/registries/pages/registries-landing/registries-landing.component.ts index 54f0b2263..ad51dc759 100644 --- a/src/app/features/registries/pages/registries-landing/registries-landing.component.ts +++ b/src/app/features/registries/pages/registries-landing/registries-landing.component.ts @@ -4,10 +4,11 @@ import { TranslatePipe } from '@ngx-translate/core'; import { Button } from 'primeng/button'; -import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; import { Router } from '@angular/router'; +import { ClearCurrentProvider } from '@core/store/provider'; import { LoadingSpinnerComponent, ResourceCardComponent, @@ -16,6 +17,7 @@ import { SubHeaderComponent, } from '@osf/shared/components'; import { ResourceType } from '@osf/shared/enums'; +import { GetRegistryProviderBrand, RegistrationProviderSelectors } from '@osf/shared/stores/registration-provider'; import { RegistryServicesComponent } from '../../components'; import { GetRegistries, RegistriesSelectors } from '../../store'; @@ -38,18 +40,30 @@ import { environment } from 'src/environments/environment'; styleUrl: './registries-landing.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class RegistriesLandingComponent implements OnInit { +export class RegistriesLandingComponent implements OnInit, OnDestroy { private router = inject(Router); - searchControl = new FormControl(''); - - private readonly actions = createDispatchMap({ getRegistries: GetRegistries }); + private actions = createDispatchMap({ + getRegistries: GetRegistries, + getProvider: GetRegistryProviderBrand, + clearCurrentProvider: ClearCurrentProvider, + }); + provider = select(RegistrationProviderSelectors.getBrandedProvider); + isProviderLoading = select(RegistrationProviderSelectors.isBrandedProviderLoading); registries = select(RegistriesSelectors.getRegistries); isRegistriesLoading = select(RegistriesSelectors.isRegistriesLoading); + searchControl = new FormControl(''); + defaultProvider = environment.defaultProvider; + ngOnInit(): void { this.actions.getRegistries(); + this.actions.getProvider(this.defaultProvider); + } + + ngOnDestroy(): void { + this.actions.clearCurrentProvider(); } redirectToSearchPageWithValue(): void { diff --git a/src/app/features/registries/pages/registries-provider-search/registries-provider-search.component.ts b/src/app/features/registries/pages/registries-provider-search/registries-provider-search.component.ts index dc80f2be6..6719967a1 100644 --- a/src/app/features/registries/pages/registries-provider-search/registries-provider-search.component.ts +++ b/src/app/features/registries/pages/registries-provider-search/registries-provider-search.component.ts @@ -2,17 +2,17 @@ import { createDispatchMap, select } from '@ngxs/store'; import { DialogService } from 'primeng/dynamicdialog'; -import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; -import { SetCurrentProvider } from '@core/store/provider'; +import { ClearCurrentProvider } from '@core/store/provider'; import { GlobalSearchComponent } from '@osf/shared/components'; import { ResourceType } from '@osf/shared/enums'; import { SetDefaultFilterValue, SetResourceType } from '@osf/shared/stores/global-search'; +import { GetRegistryProviderBrand, RegistrationProviderSelectors } from '@osf/shared/stores/registration-provider'; import { RegistryProviderHeroComponent } from '../../components/registry-provider-hero/registry-provider-hero.component'; -import { GetRegistryProviderBrand, RegistriesProviderSearchSelectors } from '../../store/registries-provider-search'; @Component({ selector: 'osf-registries-provider-search', @@ -22,18 +22,18 @@ import { GetRegistryProviderBrand, RegistriesProviderSearchSelectors } from '../ changeDetection: ChangeDetectionStrategy.OnPush, providers: [DialogService], }) -export class RegistriesProviderSearchComponent implements OnInit { +export class RegistriesProviderSearchComponent implements OnInit, OnDestroy { private route = inject(ActivatedRoute); private actions = createDispatchMap({ getProvider: GetRegistryProviderBrand, setDefaultFilterValue: SetDefaultFilterValue, setResourceType: SetResourceType, - setCurrentProvider: SetCurrentProvider, + clearCurrentProvider: ClearCurrentProvider, }); - provider = select(RegistriesProviderSearchSelectors.getBrandedProvider); - isProviderLoading = select(RegistriesProviderSearchSelectors.isBrandedProviderLoading); + provider = select(RegistrationProviderSelectors.getBrandedProvider); + isProviderLoading = select(RegistrationProviderSelectors.isBrandedProviderLoading); searchControl = new FormControl(''); @@ -44,9 +44,12 @@ export class RegistriesProviderSearchComponent implements OnInit { next: () => { this.actions.setDefaultFilterValue('publisher', this.provider()!.iri!); this.actions.setResourceType(ResourceType.Registration); - this.actions.setCurrentProvider(this.provider()!); }, }); } } + + ngOnDestroy(): void { + this.actions.clearCurrentProvider(); + } } diff --git a/src/app/features/registries/registries.routes.ts b/src/app/features/registries/registries.routes.ts index c2ce29a6f..2106ca2dd 100644 --- a/src/app/features/registries/registries.routes.ts +++ b/src/app/features/registries/registries.routes.ts @@ -5,8 +5,8 @@ import { Routes } from '@angular/router'; import { authGuard } from '@osf/core/guards'; import { RegistriesComponent } from '@osf/features/registries/registries.component'; import { RegistriesState } from '@osf/features/registries/store'; -import { RegistriesProviderSearchState } from '@osf/features/registries/store/registries-provider-search'; import { CitationsState, ContributorsState, SubjectsState } from '@osf/shared/stores'; +import { RegistrationProviderState } from '@osf/shared/stores/registration-provider'; import { LicensesHandlers, ProjectsHandlers, ProvidersHandlers } from './store/handlers'; import { FilesHandlers } from './store/handlers/files.handlers'; @@ -17,7 +17,7 @@ export const registriesRoutes: Routes = [ path: '', component: RegistriesComponent, providers: [ - provideStates([RegistriesState, CitationsState, ContributorsState, SubjectsState, RegistriesProviderSearchState]), + provideStates([RegistriesState, CitationsState, ContributorsState, SubjectsState, RegistrationProviderState]), ProvidersHandlers, ProjectsHandlers, LicensesHandlers, diff --git a/src/app/features/registries/services/index.ts b/src/app/features/registries/services/index.ts index 66cd5ab89..0dca62501 100644 --- a/src/app/features/registries/services/index.ts +++ b/src/app/features/registries/services/index.ts @@ -1,3 +1,3 @@ export * from './licenses.service'; -export * from './providers.service'; export * from './registries.service'; +export * from '@osf/shared/services/registration-provider.service'; diff --git a/src/app/features/registries/store/handlers/projects.handlers.ts b/src/app/features/registries/store/handlers/projects.handlers.ts index 659769e57..000f4898b 100644 --- a/src/app/features/registries/store/handlers/projects.handlers.ts +++ b/src/app/features/registries/store/handlers/projects.handlers.ts @@ -2,9 +2,10 @@ import { StateContext } from '@ngxs/store'; import { inject, Injectable } from '@angular/core'; +import { handleSectionError } from '@osf/shared/helpers'; import { ProjectsService } from '@osf/shared/services/projects.service'; -import { Project } from '../../models'; +import { ProjectShortInfoModel } from '../../models'; import { REGISTRIES_STATE_DEFAULTS, RegistriesStateModel } from '../registries.model'; @Injectable() @@ -28,7 +29,7 @@ export class ProjectsHandlers { }, }); return this.projectsService.fetchProjects(userId, params).subscribe({ - next: (projects: Project[]) => { + next: (projects: ProjectShortInfoModel[]) => { ctx.patchState({ projects: { data: projects, @@ -56,7 +57,7 @@ export class ProjectsHandlers { }); return this.projectsService.getComponentsTree(projectId).subscribe({ - next: (children: Project[]) => { + next: (children: ProjectShortInfoModel[]) => { ctx.patchState({ draftRegistration: { data: { @@ -68,11 +69,7 @@ export class ProjectsHandlers { }, }); }, - error: (error) => { - ctx.patchState({ - projects: { ...state.projects, isLoading: false, error }, - }); - }, + error: (error) => handleSectionError(ctx, 'draftRegistration', error), }); } } diff --git a/src/app/features/registries/store/handlers/providers.handlers.ts b/src/app/features/registries/store/handlers/providers.handlers.ts index 3e0a513e8..424d2286b 100644 --- a/src/app/features/registries/store/handlers/providers.handlers.ts +++ b/src/app/features/registries/store/handlers/providers.handlers.ts @@ -2,12 +2,12 @@ import { StateContext } from '@ngxs/store'; import { inject, Injectable } from '@angular/core'; -import { ProvidersService } from '../../services'; +import { RegistrationProviderService } from '../../services'; import { REGISTRIES_STATE_DEFAULTS, RegistriesStateModel } from '../registries.model'; @Injectable() export class ProvidersHandlers { - providersService = inject(ProvidersService); + providersService = inject(RegistrationProviderService); getProviderSchemas({ patchState }: StateContext, providerId: string) { patchState({ diff --git a/src/app/features/registries/store/registries-provider-search/index.ts b/src/app/features/registries/store/registries-provider-search/index.ts deleted file mode 100644 index f0cef0a5b..000000000 --- a/src/app/features/registries/store/registries-provider-search/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './registries-provider-search.actions'; -export * from './registries-provider-search.model'; -export * from './registries-provider-search.selectors'; -export * from './registries-provider-search.state'; diff --git a/src/app/features/registries/store/registries-provider-search/registries-provider-search.model.ts b/src/app/features/registries/store/registries-provider-search/registries-provider-search.model.ts deleted file mode 100644 index 87598b29e..000000000 --- a/src/app/features/registries/store/registries-provider-search/registries-provider-search.model.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AsyncStateModel } from '@shared/models'; - -import { RegistryProviderDetails } from '../../models'; - -export interface RegistriesProviderSearchStateModel { - currentBrandedProvider: AsyncStateModel; -} - -export const REGISTRIES_PROVIDER_SEARCH_STATE_DEFAULTS: RegistriesProviderSearchStateModel = { - currentBrandedProvider: { - data: null, - isLoading: false, - error: null, - }, -}; diff --git a/src/app/features/registries/store/registries-provider-search/registries-provider-search.selectors.ts b/src/app/features/registries/store/registries-provider-search/registries-provider-search.selectors.ts deleted file mode 100644 index 45fa310b7..000000000 --- a/src/app/features/registries/store/registries-provider-search/registries-provider-search.selectors.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Selector } from '@ngxs/store'; - -import { RegistriesProviderSearchStateModel } from './registries-provider-search.model'; -import { RegistriesProviderSearchState } from './registries-provider-search.state'; - -export class RegistriesProviderSearchSelectors { - @Selector([RegistriesProviderSearchState]) - static getBrandedProvider(state: RegistriesProviderSearchStateModel) { - return state.currentBrandedProvider.data; - } - - @Selector([RegistriesProviderSearchState]) - static isBrandedProviderLoading(state: RegistriesProviderSearchStateModel) { - return state.currentBrandedProvider.isLoading; - } -} diff --git a/src/app/features/registries/store/registries-provider-search/registries-provider-search.state.ts b/src/app/features/registries/store/registries-provider-search/registries-provider-search.state.ts deleted file mode 100644 index 14af12034..000000000 --- a/src/app/features/registries/store/registries-provider-search/registries-provider-search.state.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Action, State, StateContext } from '@ngxs/store'; -import { patch } from '@ngxs/store/operators'; - -import { catchError, tap } from 'rxjs'; - -import { inject, Injectable } from '@angular/core'; - -import { handleSectionError } from '@shared/helpers'; - -import { ProvidersService } from '../../services'; - -import { GetRegistryProviderBrand } from './registries-provider-search.actions'; -import { - REGISTRIES_PROVIDER_SEARCH_STATE_DEFAULTS, - RegistriesProviderSearchStateModel, -} from './registries-provider-search.model'; - -@State({ - name: 'registryProviderSearch', - defaults: REGISTRIES_PROVIDER_SEARCH_STATE_DEFAULTS, -}) -@Injectable() -export class RegistriesProviderSearchState { - private providersService = inject(ProvidersService); - - @Action(GetRegistryProviderBrand) - getProviderBrand(ctx: StateContext, action: GetRegistryProviderBrand) { - const state = ctx.getState(); - ctx.patchState({ - currentBrandedProvider: { - ...state.currentBrandedProvider, - isLoading: true, - }, - }); - - return this.providersService.getProviderBrand(action.providerName).pipe( - tap((brand) => { - ctx.setState( - patch({ - currentBrandedProvider: patch({ - data: brand, - isLoading: false, - error: null, - }), - }) - ); - }), - catchError((error) => handleSectionError(ctx, 'currentBrandedProvider', error)) - ); - } -} diff --git a/src/app/features/registries/store/registries.model.ts b/src/app/features/registries/store/registries.model.ts index 7c6f5e421..22a99c7e6 100644 --- a/src/app/features/registries/store/registries.model.ts +++ b/src/app/features/registries/store/registries.model.ts @@ -5,17 +5,18 @@ import { LicenseModel, OsfFile, PageSchema, + ProviderSchema, RegistrationCard, RegistrationModel, ResourceModel, SchemaResponse, } from '@shared/models'; -import { Project, ProviderSchema } from '../models'; +import { ProjectShortInfoModel } from '../models'; export interface RegistriesStateModel { providerSchemas: AsyncStateModel; - projects: AsyncStateModel; + projects: AsyncStateModel; draftRegistration: AsyncStateModel; registration: AsyncStateModel; registries: AsyncStateModel; diff --git a/src/app/features/registries/store/registries.selectors.ts b/src/app/features/registries/store/registries.selectors.ts index 57772458d..60d7f2035 100644 --- a/src/app/features/registries/store/registries.selectors.ts +++ b/src/app/features/registries/store/registries.selectors.ts @@ -5,13 +5,14 @@ import { LicenseModel, OsfFile, PageSchema, + ProviderSchema, RegistrationCard, RegistrationModel, ResourceModel, SchemaResponse, } from '@shared/models'; -import { Project, ProviderSchema } from '../models'; +import { ProjectShortInfoModel } from '../models'; import { RegistriesStateModel } from './registries.model'; import { RegistriesState } from './registries.state'; @@ -28,7 +29,7 @@ export class RegistriesSelectors { } @Selector([RegistriesState]) - static getProjects(state: RegistriesStateModel): Project[] { + static getProjects(state: RegistriesStateModel): ProjectShortInfoModel[] { return state.projects.data; } diff --git a/src/app/features/registry/components/short-registration-info/short-registration-info.component.html b/src/app/features/registry/components/short-registration-info/short-registration-info.component.html index c67597ce2..066099deb 100644 --- a/src/app/features/registry/components/short-registration-info/short-registration-info.component.html +++ b/src/app/features/registry/components/short-registration-info/short-registration-info.component.html @@ -30,6 +30,6 @@

{{ 'registry.archiving.createdDate' | translate }}

{{ 'registry.overview.metadata.associatedProject' | translate }}

- {{ associatedProjectUrl }} + {{ associatedProjectUrl }}
diff --git a/src/app/features/registry/mappers/registry-overview.mapper.ts b/src/app/features/registry/mappers/registry-overview.mapper.ts index 636a62cc1..bf7626c93 100644 --- a/src/app/features/registry/mappers/registry-overview.mapper.ts +++ b/src/app/features/registry/mappers/registry-overview.mapper.ts @@ -1,7 +1,6 @@ import { RegistryOverview, RegistryOverviewJsonApiData } from '@osf/features/registry/models'; -import { ReviewPermissionsMapper } from '@osf/shared/mappers'; +import { MapRegistryStatus, ReviewPermissionsMapper } from '@osf/shared/mappers'; import { RegistrationMapper } from '@osf/shared/mappers/registration'; -import { MapRegistryStatus } from '@shared/mappers/registry/map-registry-status.mapper'; export function MapRegistryOverview(data: RegistryOverviewJsonApiData): RegistryOverview | null { return { diff --git a/src/app/features/registry/models/registry-overview.models.ts b/src/app/features/registry/models/registry-overview.models.ts index 59aeb0e17..2b15cc715 100644 --- a/src/app/features/registry/models/registry-overview.models.ts +++ b/src/app/features/registry/models/registry-overview.models.ts @@ -5,7 +5,7 @@ import { LicenseModel, LicensesOption, MetaAnonymousJsonApi, - ProviderModel, + ProviderShortInfoModel, SchemaResponse, SubjectModel, } from '@osf/shared/models'; @@ -24,7 +24,7 @@ export interface RegistryOverview { registrationType: string; doi: string; tags: string[]; - provider?: ProviderModel; + provider?: ProviderShortInfoModel; contributors: ProjectOverviewContributor[]; citation: string; category: string; diff --git a/src/app/features/registry/pages/registry-overview/registry-overview.component.ts b/src/app/features/registry/pages/registry-overview/registry-overview.component.ts index 9c67b8a16..04f5e10c8 100644 --- a/src/app/features/registry/pages/registry-overview/registry-overview.component.ts +++ b/src/app/features/registry/pages/registry-overview/registry-overview.component.ts @@ -5,10 +5,19 @@ import { TranslatePipe, TranslateService } from '@ngx-translate/core'; import { DialogService } from 'primeng/dynamicdialog'; import { Message } from 'primeng/message'; -import { filter, map, switchMap, tap } from 'rxjs'; +import { map, switchMap, tap } from 'rxjs'; import { DatePipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, computed, DestroyRef, HostBinding, inject, signal } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + computed, + DestroyRef, + effect, + HostBinding, + inject, + signal, +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router } from '@angular/router'; @@ -177,21 +186,12 @@ export class RegistryOverviewComponent { } constructor() { - this.route.parent?.params.subscribe((params) => { - const id = params['id']; - if (id) { - this.actions - .getRegistryById(id) - .pipe( - filter(() => { - return !this.registry()?.withdrawn; - }), - tap(() => { - this.actions.getSubjects(id, ResourceType.Registration); - this.actions.getInstitutions(id); - }) - ) - .subscribe(); + effect(() => { + const registry = this.registry(); + + if (registry && !registry?.withdrawn) { + this.actions.getSubjects(registry?.id, ResourceType.Registration); + this.actions.getInstitutions(registry?.id); } }); @@ -293,6 +293,7 @@ export class RegistryOverviewComponent { this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => { this.router.navigateByUrl(currentUrl); }); + this.actions.getRegistryById(this.registry()?.id || ''); } }); diff --git a/src/app/features/registry/registry.component.ts b/src/app/features/registry/registry.component.ts index cfb39b7da..56e861b53 100644 --- a/src/app/features/registry/registry.component.ts +++ b/src/app/features/registry/registry.component.ts @@ -1,15 +1,18 @@ -import { select } from '@ngxs/store'; +import { createDispatchMap, select } from '@ngxs/store'; + +import { map, of } from 'rxjs'; import { DatePipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, DestroyRef, effect, HostBinding, inject } from '@angular/core'; -import { toObservable } from '@angular/core/rxjs-interop'; -import { RouterOutlet } from '@angular/router'; +import { ChangeDetectionStrategy, Component, DestroyRef, effect, HostBinding, inject, OnDestroy } from '@angular/core'; +import { toObservable, toSignal } from '@angular/core/rxjs-interop'; +import { ActivatedRoute, RouterOutlet } from '@angular/router'; +import { ClearCurrentProvider } from '@core/store/provider'; import { pathJoin } from '@osf/shared/helpers'; import { MetaTagsService } from '@osf/shared/services'; import { DataciteService } from '@shared/services/datacite/datacite.service'; -import { RegistryOverviewSelectors } from './store/registry-overview'; +import { GetRegistryById, RegistryOverviewSelectors } from './store/registry-overview'; import { environment } from 'src/environments/environment'; @@ -21,27 +24,46 @@ import { environment } from 'src/environments/environment'; changeDetection: ChangeDetectionStrategy.OnPush, providers: [DatePipe], }) -export class RegistryComponent { +export class RegistryComponent implements OnDestroy { @HostBinding('class') classes = 'flex-1 flex flex-column'; private readonly metaTags = inject(MetaTagsService); private readonly datePipe = inject(DatePipe); private readonly dataciteService = inject(DataciteService); private readonly destroyRef = inject(DestroyRef); + private readonly route = inject(ActivatedRoute); + + private readonly actions = createDispatchMap({ + getRegistryById: GetRegistryById, + clearCurrentProvider: ClearCurrentProvider, + }); + + private registryId = toSignal(this.route.params.pipe(map((params) => params['id'])) ?? of(undefined)); readonly registry = select(RegistryOverviewSelectors.getRegistry); readonly isRegistryLoading = select(RegistryOverviewSelectors.isRegistryLoading); readonly registry$ = toObservable(select(RegistryOverviewSelectors.getRegistry)); constructor() { + effect(() => { + if (this.registryId()) { + this.actions.getRegistryById(this.registryId()); + } + }); + effect(() => { if (!this.isRegistryLoading() && this.registry()) { this.setMetaTags(); } }); + this.dataciteService.logIdentifiableView(this.registry$).subscribe(); } + ngOnDestroy(): void { + this.actions.clearCurrentProvider(); + } + private setMetaTags(): void { this.metaTags.updateMetaTags( { diff --git a/src/app/features/registry/services/registry-overview.service.ts b/src/app/features/registry/services/registry-overview.service.ts index f06a96364..995d1d020 100644 --- a/src/app/features/registry/services/registry-overview.service.ts +++ b/src/app/features/registry/services/registry-overview.service.ts @@ -28,7 +28,7 @@ export class RegistryOverviewService { getRegistrationById(id: string): Observable { const params = { - related_counts: 'forks,comments,linked_nodes,linked_registrations,children,wikis', + related_counts: 'forks,linked_nodes,linked_registrations,children,wikis', 'embed[]': [ 'bibliographic_contributors', 'provider', diff --git a/src/app/features/registry/store/registry-overview/registry-overview.state.ts b/src/app/features/registry/store/registry-overview/registry-overview.state.ts index b940a59e0..0ead2773f 100644 --- a/src/app/features/registry/store/registry-overview/registry-overview.state.ts +++ b/src/app/features/registry/store/registry-overview/registry-overview.state.ts @@ -6,9 +6,8 @@ import { catchError } from 'rxjs/operators'; import { inject, Injectable } from '@angular/core'; import { SetCurrentProvider } from '@osf/core/store/provider/provider.actions'; -import { SetUserAsModerator } from '@osf/core/store/user'; +import { CurrentResourceType } from '@osf/shared/enums'; import { handleSectionError } from '@osf/shared/helpers'; -import { SubjectsService } from '@osf/shared/services'; import { RegistryOverviewService } from '../../services'; @@ -32,7 +31,6 @@ import { REGISTRY_OVERVIEW_DEFAULTS, RegistryOverviewStateModel } from './regist }) export class RegistryOverviewState { private readonly registryOverviewService = inject(RegistryOverviewService); - private readonly subjectsService = inject(SubjectsService); @Action(GetRegistryById) getRegistryById(ctx: StateContext, action: GetRegistryById) { @@ -45,27 +43,31 @@ export class RegistryOverviewState { }); return this.registryOverviewService.getRegistrationById(action.id).pipe( - tap({ - next: (response) => { - const registryOverview = response.registry; - if (registryOverview?.currentUserIsModerator) { - ctx.dispatch(new SetUserAsModerator()); - } - if (registryOverview?.provider) { - ctx.dispatch(new SetCurrentProvider(registryOverview.provider)); - } - ctx.patchState({ - registry: { - data: registryOverview, - isLoading: false, - error: null, - }, - isAnonymous: response.meta?.anonymous ?? false, - }); - if (registryOverview?.registrationSchemaLink && registryOverview?.questions && !action.isComponentPage) { - ctx.dispatch(new GetSchemaBlocks(registryOverview.registrationSchemaLink, registryOverview.questions)); - } - }, + tap((response) => { + const registryOverview = response.registry; + + if (registryOverview?.provider) { + ctx.dispatch( + new SetCurrentProvider({ + id: registryOverview.provider.id, + name: registryOverview.provider.name, + type: CurrentResourceType.Registrations, + permissions: registryOverview.provider.permissions, + }) + ); + } + + ctx.patchState({ + registry: { + data: registryOverview, + isLoading: false, + error: null, + }, + isAnonymous: response.meta?.anonymous ?? false, + }); + if (registryOverview?.registrationSchemaLink && registryOverview?.questions && !action.isComponentPage) { + ctx.dispatch(new GetSchemaBlocks(registryOverview.registrationSchemaLink, registryOverview.questions)); + } }), catchError((error) => handleSectionError(ctx, 'registry', error)) ); @@ -82,16 +84,14 @@ export class RegistryOverviewState { }); return this.registryOverviewService.getInstitutions(action.registryId).pipe( - tap({ - next: (institutions) => { - ctx.patchState({ - institutions: { - data: institutions, - isLoading: false, - error: null, - }, - }); - }, + tap((institutions) => { + ctx.patchState({ + institutions: { + data: institutions, + isLoading: false, + error: null, + }, + }); }), catchError((error) => handleSectionError(ctx, 'institutions', error)) ); @@ -108,16 +108,14 @@ export class RegistryOverviewState { }); return this.registryOverviewService.getSchemaBlocks(action.schemaLink).pipe( - tap({ - next: (schemaBlocks) => { - ctx.patchState({ - schemaBlocks: { - data: schemaBlocks, - isLoading: false, - error: null, - }, - }); - }, + tap((schemaBlocks) => { + ctx.patchState({ + schemaBlocks: { + data: schemaBlocks, + isLoading: false, + error: null, + }, + }); }), catchError((error) => handleSectionError(ctx, 'schemaBlocks', error)) ); @@ -134,19 +132,18 @@ export class RegistryOverviewState { }); return this.registryOverviewService.withdrawRegistration(action.registryId, action.justification).pipe( - tap({ - next: (registryOverview) => { - ctx.patchState({ - registry: { - data: registryOverview, - isLoading: false, - error: null, - }, - }); - if (registryOverview?.registrationSchemaLink && registryOverview?.questions) { - ctx.dispatch(new GetSchemaBlocks(registryOverview.registrationSchemaLink, registryOverview.questions)); - } - }, + tap((registryOverview) => { + ctx.patchState({ + registry: { + data: registryOverview, + isLoading: false, + error: null, + }, + }); + + if (registryOverview?.registrationSchemaLink && registryOverview?.questions) { + ctx.dispatch(new GetSchemaBlocks(registryOverview.registrationSchemaLink, registryOverview.questions)); + } }), catchError((error) => handleSectionError(ctx, 'registry', error)) ); @@ -163,19 +160,17 @@ export class RegistryOverviewState { }); return this.registryOverviewService.makePublic(action.registryId).pipe( - tap({ - next: (registryOverview) => { - ctx.patchState({ - registry: { - data: registryOverview, - isLoading: false, - error: null, - }, - }); - if (registryOverview?.registrationSchemaLink && registryOverview?.questions) { - ctx.dispatch(new GetSchemaBlocks(registryOverview.registrationSchemaLink, registryOverview.questions)); - } - }, + tap((registryOverview) => { + ctx.patchState({ + registry: { + data: registryOverview, + isLoading: false, + error: null, + }, + }); + if (registryOverview?.registrationSchemaLink && registryOverview?.questions) { + ctx.dispatch(new GetSchemaBlocks(registryOverview.registrationSchemaLink, registryOverview.questions)); + } }), catchError((error) => handleSectionError(ctx, 'registry', error)) ); diff --git a/src/app/shared/components/add-project-form/add-project-form.component.spec.ts b/src/app/shared/components/add-project-form/add-project-form.component.spec.ts index 0a66d2770..d20121524 100644 --- a/src/app/shared/components/add-project-form/add-project-form.component.spec.ts +++ b/src/app/shared/components/add-project-form/add-project-form.component.spec.ts @@ -17,7 +17,7 @@ import { ProjectFormControls } from '@osf/shared/enums'; import { CustomValidators } from '@osf/shared/helpers'; import { MOCK_STORE, MOCK_USER } from '@osf/shared/mocks'; import { ProjectForm } from '@osf/shared/models'; -import { Project } from '@osf/shared/models/projects'; +import { ProjectModel } from '@osf/shared/models/projects'; import { GetMyProjects, MyResourcesState } from '@osf/shared/stores'; import { AffiliatedInstitutionSelectComponent, ProjectSelectorComponent } from '@shared/components'; import { InstitutionsState } from '@shared/stores/institutions'; @@ -114,7 +114,7 @@ describe('AddProjectFormComponent', () => { }); it('should update template when onTemplateChange is called with a project', () => { - const mockProject: Project = { id: 'template1', title: 'Template Project' } as Project; + const mockProject: ProjectModel = { id: 'template1', title: 'Template Project' } as ProjectModel; const templateControl = component.projectForm().get(ProjectFormControls.Template); expect(templateControl?.value).toBe(''); diff --git a/src/app/shared/components/add-project-form/add-project-form.component.ts b/src/app/shared/components/add-project-form/add-project-form.component.ts index 13e361a37..0c8072372 100644 --- a/src/app/shared/components/add-project-form/add-project-form.component.ts +++ b/src/app/shared/components/add-project-form/add-project-form.component.ts @@ -16,7 +16,7 @@ import { FormGroup, ReactiveFormsModule } from '@angular/forms'; import { UserSelectors } from '@core/store/user'; import { ProjectFormControls } from '@osf/shared/enums'; import { Institution, ProjectForm } from '@osf/shared/models'; -import { Project } from '@osf/shared/models/projects'; +import { ProjectModel } from '@osf/shared/models/projects'; import { FetchRegions, RegionsSelectors } from '@osf/shared/stores'; import { FetchUserInstitutions, InstitutionsSelectors } from '@osf/shared/stores/institutions'; @@ -51,7 +51,7 @@ export class AddProjectFormComponent implements OnInit { ProjectFormControls = ProjectFormControls; hasTemplateSelected = signal(false); - selectedTemplate = signal(null); + selectedTemplate = signal(null); isSubmitting = signal(false); selectedAffiliations = signal([]); currentUser = select(UserSelectors.getCurrentUser); @@ -92,7 +92,7 @@ export class AddProjectFormComponent implements OnInit { .subscribe((value) => this.hasTemplateSelected.set(!!value)); } - onTemplateChange(project: Project | null): void { + onTemplateChange(project: ProjectModel | null): void { if (!project) return; this.selectedTemplate.set(project); this.projectForm().get(ProjectFormControls.Template)?.setValue(project.id); diff --git a/src/app/shared/components/project-selector/project-selector.component.ts b/src/app/shared/components/project-selector/project-selector.component.ts index fdf5ab030..b9a4ddf78 100644 --- a/src/app/shared/components/project-selector/project-selector.component.ts +++ b/src/app/shared/components/project-selector/project-selector.component.ts @@ -23,7 +23,7 @@ import { FormsModule } from '@angular/forms'; import { UserSelectors } from '@core/store/user'; import { CustomOption } from '@shared/models'; -import { Project } from '@shared/models/projects'; +import { ProjectModel } from '@shared/models/projects'; import { GetProjects } from '@shared/stores'; import { ProjectsSelectors } from '@shared/stores/projects/projects.selectors'; @@ -46,12 +46,12 @@ export class ProjectSelectorComponent { placeholder = input('common.buttons.select'); showClear = input(true); excludeProjectIds = input([]); - selectedProject = model(null); + selectedProject = model(null); - projectChange = output(); - projectsLoaded = output(); + projectChange = output(); + projectsLoaded = output(); - projectsOptions = signal[]>([]); + projectsOptions = signal[]>([]); filterMessage = computed(() => { const isLoading = this.isProjectsLoading(); diff --git a/src/app/shared/components/scheduled-banner/schedule-banner.component.spec.ts b/src/app/shared/components/scheduled-banner/schedule-banner.component.spec.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/app/shared/components/scheduled-banner/scheduled-banner.component.html b/src/app/shared/components/scheduled-banner/scheduled-banner.component.html index d1cac0cdd..04a6b6fc7 100644 --- a/src/app/shared/components/scheduled-banner/scheduled-banner.component.html +++ b/src/app/shared/components/scheduled-banner/scheduled-banner.component.html @@ -1,22 +1,14 @@ -@if (this.shouldShowBanner()) { +@if (shouldShowBanner()) { } diff --git a/src/app/shared/components/scheduled-banner/scheduled-banner.component.ts b/src/app/shared/components/scheduled-banner/scheduled-banner.component.ts index 996842773..171545415 100644 --- a/src/app/shared/components/scheduled-banner/scheduled-banner.component.ts +++ b/src/app/shared/components/scheduled-banner/scheduled-banner.component.ts @@ -24,9 +24,9 @@ export class ScheduledBannerComponent implements OnInit { const banner = this.currentBanner(); if (banner) { const bannerStartTime = banner.startDate; - const bannderEndTime = banner.endDate; + const bannerEndTime = banner.endDate; const currentTime = new Date(); - return bannerStartTime < currentTime && bannderEndTime > currentTime; + return bannerStartTime < currentTime && bannerEndTime > currentTime; } return false; }); diff --git a/src/app/shared/enums/resource-type.enum.ts b/src/app/shared/enums/resource-type.enum.ts index 82e39135a..5a8ff84d9 100644 --- a/src/app/shared/enums/resource-type.enum.ts +++ b/src/app/shared/enums/resource-type.enum.ts @@ -17,4 +17,5 @@ export enum CurrentResourceType { Projects = 'nodes', Registrations = 'registrations', Preprints = 'preprints', + Collections = 'collections', } diff --git a/src/app/shared/mappers/index.ts b/src/app/shared/mappers/index.ts index 1c6d170c7..5579fbd32 100644 --- a/src/app/shared/mappers/index.ts +++ b/src/app/shared/mappers/index.ts @@ -13,6 +13,7 @@ export * from './institutions'; export * from './licenses.mapper'; export * from './nodes'; export * from './notification-subscription.mapper'; +export * from './registration-provider.mapper'; export * from './registry'; export * from './resource-overview.mappers'; export * from './review-actions.mapper'; diff --git a/src/app/shared/mappers/projects/projects.mapper.ts b/src/app/shared/mappers/projects/projects.mapper.ts index a92d6e8a7..86ee52697 100644 --- a/src/app/shared/mappers/projects/projects.mapper.ts +++ b/src/app/shared/mappers/projects/projects.mapper.ts @@ -1,13 +1,13 @@ import { CollectionSubmissionMetadataPayloadJsonApi } from '@osf/features/collections/models'; import { ProjectMetadataUpdatePayload } from '@osf/shared/models'; -import { Project, ProjectJsonApi, ProjectsResponseJsonApi } from '@osf/shared/models/projects'; +import { ProjectJsonApi, ProjectModel, ProjectsResponseJsonApi } from '@osf/shared/models/projects'; export class ProjectsMapper { - static fromGetAllProjectsResponse(response: ProjectsResponseJsonApi): Project[] { + static fromGetAllProjectsResponse(response: ProjectsResponseJsonApi): ProjectModel[] { return response.data.map((project) => this.fromProjectResponse(project)); } - static fromProjectResponse(project: ProjectJsonApi): Project { + static fromProjectResponse(project: ProjectJsonApi): ProjectModel { return { id: project.id, type: project.type, diff --git a/src/app/shared/mappers/registration-provider.mapper.ts b/src/app/shared/mappers/registration-provider.mapper.ts new file mode 100644 index 000000000..78b9f8dc3 --- /dev/null +++ b/src/app/shared/mappers/registration-provider.mapper.ts @@ -0,0 +1,39 @@ +import { + ProviderSchema, + ProvidersResponseJsonApi, + RegistryProviderDetails, + RegistryProviderDetailsJsonApi, +} from '@osf/shared/models'; + +export class RegistrationProviderMapper { + static fromProvidersResponse(response: ProvidersResponseJsonApi): ProviderSchema[] { + return response.data.map((item) => ({ + id: item.id, + name: item.attributes.name, + })); + } + + static fromRegistryProvider(response: RegistryProviderDetailsJsonApi): RegistryProviderDetails { + const brandRaw = response.embeds!.brand.data; + + return { + id: response.id, + name: response.attributes.name, + descriptionHtml: response.attributes.description, + permissions: response.attributes.permissions, + brand: brandRaw + ? { + id: brandRaw.id, + name: brandRaw.attributes.name, + heroLogoImageUrl: brandRaw.attributes.hero_logo_image, + heroBackgroundImageUrl: brandRaw.attributes.hero_background_image, + topNavLogoImageUrl: brandRaw.attributes.topnav_logo_image, + primaryColor: brandRaw.attributes.primary_color, + secondaryColor: brandRaw.attributes.secondary_color, + backgroundColor: brandRaw.attributes.background_color, + } + : null, + iri: response.links.iri, + }; + } +} diff --git a/src/app/shared/models/collections/collections-json-api.models.ts b/src/app/shared/models/collections/collections-json-api.models.ts index 69e6384ad..65c1b067d 100644 --- a/src/app/shared/models/collections/collections-json-api.models.ts +++ b/src/app/shared/models/collections/collections-json-api.models.ts @@ -1,31 +1,9 @@ -import { BrandDataJsonApi, JsonApiResponse } from '@shared/models'; +import { BrandDataJsonApi, CollectionsProviderAttributesJsonApi, JsonApiResponse } from '@shared/models'; export interface CollectionProviderResponseJsonApi { id: string; type: string; - attributes: { - name: string; - description: string; - advisory_board: string; - example: string | null; - domain: string; - domain_redirect_enabled: boolean; - footer_links: string; - email_support: boolean | null; - facebook_app_id: string | null; - allow_submissions: boolean; - allow_commenting: boolean; - assets: { - style?: string; - square_color_transparent?: string; - square_color_no_transparent?: string; - favicon?: string; - }; - share_source: string; - share_publish_type: string; - permissions: string[]; - reviews_workflow: string; - }; + attributes: CollectionsProviderAttributesJsonApi; embeds: { brand: { data?: BrandDataJsonApi; @@ -38,12 +16,6 @@ export interface CollectionProviderResponseJsonApi { type: string; }; }; - brand: { - data: { - id: string; - type: string; - } | null; - }; }; } diff --git a/src/app/shared/models/collections/collections.models.ts b/src/app/shared/models/collections/collections.models.ts index ff94c7df3..ccd9429fe 100644 --- a/src/app/shared/models/collections/collections.models.ts +++ b/src/app/shared/models/collections/collections.models.ts @@ -1,30 +1,15 @@ import { CollectionSubmissionReviewAction } from '@osf/features/moderation/models'; -import { Brand } from '@shared/models'; -export interface CollectionProvider { - id: string; - type: string; - name: string; - description: string; - advisoryBoard: string; - example: string | null; - domain: string; - domainRedirectEnabled: boolean; - footerLinks: string; - emailSupport: boolean | null; - facebookAppId: string | null; - allowSubmissions: boolean; - allowCommenting: boolean; +import { Brand } from '../brand.model'; +import { BaseProviderModel } from '../provider'; + +export interface CollectionProvider extends BaseProviderModel { assets: { style?: string; squareColorTransparent?: string; squareColorNoTransparent?: string; favicon?: string; }; - shareSource: string; - sharePublishType: string; - permissions: string[]; - reviewsWorkflow: string; primaryCollection: { id: string; type: string; diff --git a/src/app/shared/models/projects/projects.models.ts b/src/app/shared/models/projects/projects.models.ts index 2e41b948e..68be33269 100644 --- a/src/app/shared/models/projects/projects.models.ts +++ b/src/app/shared/models/projects/projects.models.ts @@ -2,7 +2,7 @@ import { StringOrNull } from '@osf/shared/helpers'; import { LicenseOptions } from '../license.model'; -export interface Project { +export interface ProjectModel { id: string; type: string; title: string; diff --git a/src/app/shared/models/provider/base-provider-json-api.model.ts b/src/app/shared/models/provider/base-provider-json-api.model.ts index 74ce19065..09c066b9c 100644 --- a/src/app/shared/models/provider/base-provider-json-api.model.ts +++ b/src/app/shared/models/provider/base-provider-json-api.model.ts @@ -1,19 +1,19 @@ import { ReviewPermissions } from '@osf/shared/enums'; export interface BaseProviderAttributesJsonApi { - name: string; - description: string; advisory_board: string; - example: string | null; + allow_commenting: boolean; + allow_submissions: boolean; + description: string; domain: string; domain_redirect_enabled: boolean; - footer_links: string; email_support: string | null; + example: string | null; facebook_app_id: string | null; - allow_submissions: boolean; - allow_commenting: boolean; - share_source: string; - share_publish_type: string; + footer_links: string; + name: string; permissions: ReviewPermissions[]; reviews_workflow: string; + share_publish_type: string; + share_source: string; } diff --git a/src/app/shared/models/provider/collections-provider-json-api.model.ts b/src/app/shared/models/provider/collections-provider-json-api.model.ts new file mode 100644 index 000000000..d2b6a4de0 --- /dev/null +++ b/src/app/shared/models/provider/collections-provider-json-api.model.ts @@ -0,0 +1,12 @@ +import { BaseProviderAttributesJsonApi } from './base-provider-json-api.model'; + +export interface CollectionsProviderAttributesJsonApi extends BaseProviderAttributesJsonApi { + assets: CollectionsAssetsJsonApi; +} + +export interface CollectionsAssetsJsonApi { + favicon: string; + style: string; + square_color_no_transparent: string; + square_color_transparent: string; +} diff --git a/src/app/shared/models/provider/index.ts b/src/app/shared/models/provider/index.ts index 7750fce62..5968ef0a6 100644 --- a/src/app/shared/models/provider/index.ts +++ b/src/app/shared/models/provider/index.ts @@ -1,5 +1,7 @@ export * from './base-provider-json-api.model'; +export * from './collections-provider-json-api.model'; export * from './preprints-provider-json-api.model'; export * from './provider.model'; export * from './providers-json-api.model'; export * from './registration-provider-json-api.model'; +export * from './registry-provider.model'; diff --git a/src/app/shared/models/provider/preprints-provider-json-api.model.ts b/src/app/shared/models/provider/preprints-provider-json-api.model.ts index 4bd8d70a2..e49d23559 100644 --- a/src/app/shared/models/provider/preprints-provider-json-api.model.ts +++ b/src/app/shared/models/provider/preprints-provider-json-api.model.ts @@ -1,17 +1,17 @@ import { BaseProviderAttributesJsonApi } from './base-provider-json-api.model'; export interface PreprintProviderAttributesJsonApi extends BaseProviderAttributesJsonApi { - assets: PreprintProviderAssetsJsonApi; - preprint_word: string; additional_providers: string[]; - assertions_enabled: boolean; advertise_on_discover_page: boolean; - reviews_comments_private: boolean; + assertions_enabled: boolean; + assets: PreprintProviderAssetsJsonApi; + preprint_word: string; reviews_comments_anonymous: boolean; + reviews_comments_private: boolean; } export interface PreprintProviderAssetsJsonApi { favicon: string; - wide_white: string; square_color_no_transparent: string; + wide_white: string; } diff --git a/src/app/shared/models/provider/provider.model.ts b/src/app/shared/models/provider/provider.model.ts index 249342ac4..88dc359c1 100644 --- a/src/app/shared/models/provider/provider.model.ts +++ b/src/app/shared/models/provider/provider.model.ts @@ -1,7 +1,28 @@ -import { ReviewPermissions } from '@osf/shared/enums'; +import { CurrentResourceType, ReviewPermissions } from '@osf/shared/enums'; -export interface ProviderModel { +export interface ProviderShortInfoModel { id: string; name: string; + type: CurrentResourceType; permissions?: ReviewPermissions[]; } + +export interface BaseProviderModel { + id: string; + type: string; + advisoryBoard: string; + allowCommenting: boolean; + allowSubmissions: boolean; + description: string; + domain: string; + domainRedirectEnabled: boolean; + emailSupport: string | null; + example: string | null; + facebookAppId: string | null; + footerLinks: string; + name: string; + permissions: ReviewPermissions[]; + reviewsWorkflow: string; + sharePublishType: string; + shareSource: string; +} diff --git a/src/app/shared/models/provider/registration-provider-json-api.model.ts b/src/app/shared/models/provider/registration-provider-json-api.model.ts index 9d865b1dc..b78154747 100644 --- a/src/app/shared/models/provider/registration-provider-json-api.model.ts +++ b/src/app/shared/models/provider/registration-provider-json-api.model.ts @@ -1,12 +1,14 @@ +import { BrandDataJsonApi } from '../brand.json-api.model'; + import { BaseProviderAttributesJsonApi } from './base-provider-json-api.model'; export interface RegistrationProviderAttributesJsonApi extends BaseProviderAttributesJsonApi { + allow_bulk_uploads: boolean; + allow_updates: boolean; assets: RegistrationAssetsJsonApi; branded_discovery_page: boolean; - reviews_comments_anonymous: boolean | null; - allow_updates: boolean; - allow_bulk_uploads: boolean; registration_word: string; + reviews_comments_anonymous: boolean | null; } export interface RegistrationAssetsJsonApi { @@ -14,3 +16,17 @@ export interface RegistrationAssetsJsonApi { square_color_transparent: string; wide_color: string; } + +export interface RegistryProviderDetailsJsonApi { + id: string; + type: 'registration-providers'; + attributes: RegistrationProviderAttributesJsonApi; + embeds?: { + brand: { + data: BrandDataJsonApi; + }; + }; + links: { + iri: string; + }; +} diff --git a/src/app/features/registries/models/registry-provider.model.ts b/src/app/shared/models/provider/registry-provider.model.ts similarity index 91% rename from src/app/features/registries/models/registry-provider.model.ts rename to src/app/shared/models/provider/registry-provider.model.ts index 132be2d27..3c2f82ec6 100644 --- a/src/app/features/registries/models/registry-provider.model.ts +++ b/src/app/shared/models/provider/registry-provider.model.ts @@ -6,6 +6,6 @@ export interface RegistryProviderDetails { name: string; descriptionHtml: string; permissions: ReviewPermissions[]; - brand: Brand; + brand: Brand | null; iri: string; } diff --git a/src/app/shared/models/registration/draft-registration.model.ts b/src/app/shared/models/registration/draft-registration.model.ts index 7b40cf319..e9cbd53ef 100644 --- a/src/app/shared/models/registration/draft-registration.model.ts +++ b/src/app/shared/models/registration/draft-registration.model.ts @@ -1,5 +1,5 @@ import { LicenseOptions } from '../license.model'; -import { Project } from '../projects'; +import { ProjectModel } from '../projects'; export interface DraftRegistrationModel { id: string; @@ -13,8 +13,8 @@ export interface DraftRegistrationModel { tags: string[]; // eslint-disable-next-line @typescript-eslint/no-explicit-any stepsData?: Record; - branchedFrom?: Partial; + branchedFrom?: Partial; providerId: string; hasProject: boolean; - components: Partial[]; + components: Partial[]; } diff --git a/src/app/shared/models/registration/index.ts b/src/app/shared/models/registration/index.ts index cdbda99e1..af928d02e 100644 --- a/src/app/shared/models/registration/index.ts +++ b/src/app/shared/models/registration/index.ts @@ -1,5 +1,6 @@ export * from './draft-registration.model'; export * from './page-schema.model'; +export * from './provider-schema.model'; export * from './registration.model'; export * from './registration-card.model'; export * from './registration-json-api.model'; diff --git a/src/app/features/registries/models/provider-schema.model.ts b/src/app/shared/models/registration/provider-schema.model.ts similarity index 100% rename from src/app/features/registries/models/provider-schema.model.ts rename to src/app/shared/models/registration/provider-schema.model.ts diff --git a/src/app/shared/services/addons/addons.service.spec.ts b/src/app/shared/services/addons/addons.service.spec.ts index 334562221..d8b27feb7 100644 --- a/src/app/shared/services/addons/addons.service.spec.ts +++ b/src/app/shared/services/addons/addons.service.spec.ts @@ -121,7 +121,7 @@ describe('Service: Addons', () => { [HttpTestingController], (httpMock: HttpTestingController) => { let results; - service.getAuthorizedStorageOauthToken('account-id').subscribe((result) => { + service.getAuthorizedStorageOauthToken('account-id', 'storage').subscribe((result) => { results = result; }); diff --git a/src/app/shared/services/index.ts b/src/app/shared/services/index.ts index 6a46dd119..b5182241d 100644 --- a/src/app/shared/services/index.ts +++ b/src/app/shared/services/index.ts @@ -18,6 +18,7 @@ export { MyResourcesService } from './my-resources.service'; export { NodeLinksService } from './node-links.service'; export { ProjectRedirectDialogService } from './project-redirect-dialog.service'; export { RegionsService } from './regions.service'; +export { RegistrationProviderService } from './registration-provider.service'; export { ResourceGuidService } from './resource.service'; export { ResourceCardService } from './resource-card.service'; export { SocialShareService } from './social-share.service'; diff --git a/src/app/shared/services/projects.service.ts b/src/app/shared/services/projects.service.ts index ae135a51a..ada1df3a6 100644 --- a/src/app/shared/services/projects.service.ts +++ b/src/app/shared/services/projects.service.ts @@ -4,7 +4,7 @@ import { inject, Injectable } from '@angular/core'; import { ProjectsMapper } from '@shared/mappers/projects'; import { ProjectMetadataUpdatePayload } from '@shared/models'; -import { Project, ProjectJsonApi, ProjectsResponseJsonApi } from '@shared/models/projects'; +import { ProjectJsonApi, ProjectModel, ProjectsResponseJsonApi } from '@shared/models/projects'; import { JsonApiService } from '@shared/services'; import { environment } from 'src/environments/environment'; @@ -16,13 +16,13 @@ export class ProjectsService { private jsonApiService = inject(JsonApiService); private apiUrl = `${environment.apiDomainUrl}/v2`; - fetchProjects(userId: string, params?: Record): Observable { + fetchProjects(userId: string, params?: Record): Observable { return this.jsonApiService .get(`${this.apiUrl}/users/${userId}/nodes/`, params) .pipe(map((response) => ProjectsMapper.fromGetAllProjectsResponse(response))); } - updateProjectMetadata(metadata: ProjectMetadataUpdatePayload): Observable { + updateProjectMetadata(metadata: ProjectMetadataUpdatePayload): Observable { const payload = ProjectsMapper.toUpdateProjectRequest(metadata); return this.jsonApiService @@ -30,13 +30,13 @@ export class ProjectsService { .pipe(map((response) => ProjectsMapper.fromProjectResponse(response))); } - getProjectChildren(id: string): Observable { + getProjectChildren(id: string): Observable { return this.jsonApiService .get(`${this.apiUrl}/nodes/${id}/children/`) .pipe(map((response) => ProjectsMapper.fromGetAllProjectsResponse(response))); } - getComponentsTree(id: string): Observable { + getComponentsTree(id: string): Observable { return this.getProjectChildren(id).pipe( switchMap((children) => { if (!children.length) { diff --git a/src/app/features/registries/services/providers.service.ts b/src/app/shared/services/registration-provider.service.ts similarity index 53% rename from src/app/features/registries/services/providers.service.ts rename to src/app/shared/services/registration-provider.service.ts index 978251eb0..53d6a7c55 100644 --- a/src/app/features/registries/services/providers.service.ts +++ b/src/app/shared/services/registration-provider.service.ts @@ -2,28 +2,30 @@ import { map, Observable } from 'rxjs'; import { inject, Injectable } from '@angular/core'; -import { RegistryProviderDetails } from '@osf/features/registries/models/registry-provider.model'; -import { RegistryProviderDetailsJsonApi } from '@osf/features/registries/models/registry-provider-json-api.model'; -import { ProvidersResponseJsonApi } from '@osf/shared/models'; -import { JsonApiService } from '@osf/shared/services'; -import { JsonApiResponse } from '@shared/models'; +import { RegistrationProviderMapper } from '../mappers'; +import { + JsonApiResponse, + ProviderSchema, + ProvidersResponseJsonApi, + RegistryProviderDetails, + RegistryProviderDetailsJsonApi, +} from '../models'; -import { ProvidersMapper } from '../mappers/providers.mapper'; -import { ProviderSchema } from '../models'; +import { JsonApiService } from './'; import { environment } from 'src/environments/environment'; @Injectable({ providedIn: 'root', }) -export class ProvidersService { +export class RegistrationProviderService { private readonly jsonApiService = inject(JsonApiService); private readonly apiUrl = `${environment.apiDomainUrl}/v2`; getProviderSchemas(providerId: string): Observable { return this.jsonApiService .get(`${this.apiUrl}/providers/registrations/${providerId}/schemas/`) - .pipe(map((response) => ProvidersMapper.fromProvidersResponse(response))); + .pipe(map((response) => RegistrationProviderMapper.fromProvidersResponse(response))); } getProviderBrand(providerName: string): Observable { @@ -31,6 +33,6 @@ export class ProvidersService { .get< JsonApiResponse >(`${this.apiUrl}/providers/registrations/${providerName}/?embed=brand`) - .pipe(map((response) => ProvidersMapper.fromRegistryProvider(response.data))); + .pipe(map((response) => RegistrationProviderMapper.fromRegistryProvider(response.data))); } } diff --git a/src/app/shared/stores/addons/addons.state.spec.ts b/src/app/shared/stores/addons/addons.state.spec.ts index 1c71b922d..c9c5736b6 100644 --- a/src/app/shared/stores/addons/addons.state.spec.ts +++ b/src/app/shared/stores/addons/addons.state.spec.ts @@ -280,7 +280,7 @@ describe('State: Addons', () => { [HttpTestingController], (httpMock: HttpTestingController) => { let result: any[] = []; - store.dispatch(new GetAuthorizedStorageOauthToken('account-id')).subscribe(() => { + store.dispatch(new GetAuthorizedStorageOauthToken('account-id', 'storage')).subscribe(() => { result = store.selectSnapshot(AddonsSelectors.getAuthorizedStorageAddons); }); @@ -327,7 +327,7 @@ describe('State: Addons', () => { let result: any[] = []; store.dispatch(new GetAuthorizedStorageAddons('reference-id')).subscribe(); - store.dispatch(new GetAuthorizedStorageOauthToken('account-id')).subscribe(() => { + store.dispatch(new GetAuthorizedStorageOauthToken('account-id', 'storage')).subscribe(() => { result = store.selectSnapshot(AddonsSelectors.getAuthorizedStorageAddons); }); @@ -383,7 +383,7 @@ describe('State: Addons', () => { (httpMock: HttpTestingController) => { let result: any = null; - store.dispatch(new GetAuthorizedStorageOauthToken('account-id')).subscribe({ + store.dispatch(new GetAuthorizedStorageOauthToken('account-id', 'storage')).subscribe({ next: () => { result = 'Expected error, but got success'; }, diff --git a/src/app/shared/stores/collections/collections.state.ts b/src/app/shared/stores/collections/collections.state.ts index 7c6289a54..2024ec572 100644 --- a/src/app/shared/stores/collections/collections.state.ts +++ b/src/app/shared/stores/collections/collections.state.ts @@ -4,6 +4,8 @@ import { catchError, forkJoin, of, switchMap, tap } from 'rxjs'; import { inject, Injectable } from '@angular/core'; +import { SetCurrentProvider } from '@core/store/provider'; +import { CurrentResourceType } from '@osf/shared/enums'; import { handleSectionError } from '@osf/shared/helpers'; import { CollectionsService } from '@osf/shared/services'; @@ -51,6 +53,21 @@ export class CollectionsState { }, }); + const provider = state.collectionProvider.data; + + if (provider?.name === action.collectionName) { + ctx.dispatch( + new SetCurrentProvider({ + id: provider.id, + name: provider.name, + type: CurrentResourceType.Collections, + permissions: provider.permissions, + }) + ); + + return of(provider); + } + return this.collectionsService.getCollectionProvider(action.collectionName).pipe( tap((res) => { ctx.patchState({ @@ -60,6 +77,15 @@ export class CollectionsState { error: null, }, }); + + ctx.dispatch( + new SetCurrentProvider({ + id: res.id, + name: res.name, + type: CurrentResourceType.Collections, + permissions: res.permissions, + }) + ); }) ); } diff --git a/src/app/shared/stores/contributors/contributors.actions.ts b/src/app/shared/stores/contributors/contributors.actions.ts index 40626b7d2..d93e9c89b 100644 --- a/src/app/shared/stores/contributors/contributors.actions.ts +++ b/src/app/shared/stores/contributors/contributors.actions.ts @@ -10,7 +10,7 @@ export class GetAllContributors { ) {} } -export class UpdateSearchValue { +export class UpdateContributorsSearchValue { static readonly type = '[Contributors] Update Search Value'; constructor(public searchValue: string | null) {} diff --git a/src/app/shared/stores/contributors/contributors.state.ts b/src/app/shared/stores/contributors/contributors.state.ts index 4009b8965..bfd7558d2 100644 --- a/src/app/shared/stores/contributors/contributors.state.ts +++ b/src/app/shared/stores/contributors/contributors.state.ts @@ -16,8 +16,8 @@ import { SearchUsers, UpdateBibliographyFilter, UpdateContributor, + UpdateContributorsSearchValue, UpdatePermissionFilter, - UpdateSearchValue, } from './contributors.actions'; import { CONTRIBUTORS_STATE_DEFAULTS, ContributorsStateModel } from './contributors.model'; @@ -141,8 +141,8 @@ export class ContributorsState { ); } - @Action(UpdateSearchValue) - updateSearchValue(ctx: StateContext, action: UpdateSearchValue) { + @Action(UpdateContributorsSearchValue) + updateContributorsSearchValue(ctx: StateContext, action: UpdateContributorsSearchValue) { ctx.patchState({ contributorsList: { ...ctx.getState().contributorsList, searchValue: action.searchValue } }); } diff --git a/src/app/shared/stores/projects/projects.actions.ts b/src/app/shared/stores/projects/projects.actions.ts index e5fa9b65a..3a7731e12 100644 --- a/src/app/shared/stores/projects/projects.actions.ts +++ b/src/app/shared/stores/projects/projects.actions.ts @@ -1,5 +1,5 @@ import { ProjectMetadataUpdatePayload } from '@shared/models'; -import { Project } from '@shared/models/projects'; +import { ProjectModel } from '@shared/models/projects'; export class GetProjects { static readonly type = '[Projects] Get Projects'; @@ -13,7 +13,7 @@ export class GetProjects { export class SetSelectedProject { static readonly type = '[Projects] Set Selected Project'; - constructor(public project: Project) {} + constructor(public project: ProjectModel) {} } export class UpdateProjectMetadata { diff --git a/src/app/shared/stores/projects/projects.model.ts b/src/app/shared/stores/projects/projects.model.ts index cca209928..cb8bb5250 100644 --- a/src/app/shared/stores/projects/projects.model.ts +++ b/src/app/shared/stores/projects/projects.model.ts @@ -1,8 +1,8 @@ -import { AsyncStateModel, Project } from '@osf/shared/models'; +import { AsyncStateModel, ProjectModel } from '@osf/shared/models'; export interface ProjectsStateModel { - projects: AsyncStateModel; - selectedProject: AsyncStateModel; + projects: AsyncStateModel; + selectedProject: AsyncStateModel; } export const PROJECTS_STATE_DEFAULTS: ProjectsStateModel = { diff --git a/src/app/shared/stores/registration-provider/index.ts b/src/app/shared/stores/registration-provider/index.ts new file mode 100644 index 000000000..47e5b0316 --- /dev/null +++ b/src/app/shared/stores/registration-provider/index.ts @@ -0,0 +1,4 @@ +export * from './registration-provider.actions'; +export * from './registration-provider.model'; +export * from './registration-provider.selectors'; +export * from './registration-provider.state'; diff --git a/src/app/features/registries/store/registries-provider-search/registries-provider-search.actions.ts b/src/app/shared/stores/registration-provider/registration-provider.actions.ts similarity index 100% rename from src/app/features/registries/store/registries-provider-search/registries-provider-search.actions.ts rename to src/app/shared/stores/registration-provider/registration-provider.actions.ts diff --git a/src/app/shared/stores/registration-provider/registration-provider.model.ts b/src/app/shared/stores/registration-provider/registration-provider.model.ts new file mode 100644 index 000000000..00928acbc --- /dev/null +++ b/src/app/shared/stores/registration-provider/registration-provider.model.ts @@ -0,0 +1,13 @@ +import { AsyncStateModel, RegistryProviderDetails } from '@shared/models'; + +export interface RegistrationProviderStateModel { + currentBrandedProvider: AsyncStateModel; +} + +export const REGISTRIES_PROVIDER_SEARCH_STATE_DEFAULTS: RegistrationProviderStateModel = { + currentBrandedProvider: { + data: null, + isLoading: false, + error: null, + }, +}; diff --git a/src/app/shared/stores/registration-provider/registration-provider.selectors.ts b/src/app/shared/stores/registration-provider/registration-provider.selectors.ts new file mode 100644 index 000000000..61010f5cb --- /dev/null +++ b/src/app/shared/stores/registration-provider/registration-provider.selectors.ts @@ -0,0 +1,16 @@ +import { Selector } from '@ngxs/store'; + +import { RegistrationProviderStateModel } from './registration-provider.model'; +import { RegistrationProviderState } from './registration-provider.state'; + +export class RegistrationProviderSelectors { + @Selector([RegistrationProviderState]) + static getBrandedProvider(state: RegistrationProviderStateModel) { + return state.currentBrandedProvider.data; + } + + @Selector([RegistrationProviderState]) + static isBrandedProviderLoading(state: RegistrationProviderStateModel) { + return state.currentBrandedProvider.isLoading; + } +} diff --git a/src/app/shared/stores/registration-provider/registration-provider.state.ts b/src/app/shared/stores/registration-provider/registration-provider.state.ts new file mode 100644 index 000000000..9aa723ae9 --- /dev/null +++ b/src/app/shared/stores/registration-provider/registration-provider.state.ts @@ -0,0 +1,78 @@ +import { Action, State, StateContext } from '@ngxs/store'; +import { patch } from '@ngxs/store/operators'; + +import { catchError, of, tap } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { SetCurrentProvider } from '@core/store/provider'; +import { CurrentResourceType } from '@osf/shared/enums'; +import { handleSectionError } from '@shared/helpers'; + +import { RegistrationProviderService } from '../../services'; + +import { GetRegistryProviderBrand } from './registration-provider.actions'; +import { + RegistrationProviderStateModel as RegistrationProviderStateModel, + REGISTRIES_PROVIDER_SEARCH_STATE_DEFAULTS, +} from './registration-provider.model'; + +@State({ + name: 'registryProviderSearch', + defaults: REGISTRIES_PROVIDER_SEARCH_STATE_DEFAULTS, +}) +@Injectable() +export class RegistrationProviderState { + private registrationProvidersService = inject(RegistrationProviderService); + + @Action(GetRegistryProviderBrand) + getProviderBrand(ctx: StateContext, action: GetRegistryProviderBrand) { + const state = ctx.getState(); + + const currentProvider = state.currentBrandedProvider.data; + + if (currentProvider?.name === action.providerName) { + ctx.dispatch( + new SetCurrentProvider({ + id: currentProvider.id, + name: currentProvider.name, + type: CurrentResourceType.Registrations, + permissions: currentProvider.permissions, + }) + ); + + return of(currentProvider); + } + + ctx.patchState({ + currentBrandedProvider: { + ...state.currentBrandedProvider, + isLoading: true, + }, + }); + + return this.registrationProvidersService.getProviderBrand(action.providerName).pipe( + tap((provider) => { + ctx.setState( + patch({ + currentBrandedProvider: patch({ + data: provider, + isLoading: false, + error: null, + }), + }) + ); + + ctx.dispatch( + new SetCurrentProvider({ + id: provider.id, + name: provider.name, + type: CurrentResourceType.Registrations, + permissions: provider.permissions, + }) + ); + }), + catchError((error) => handleSectionError(ctx, 'currentBrandedProvider', error)) + ); + } +}