From 0cc74308d9947c65987a19ea1cda8bf5f3a5214b Mon Sep 17 00:00:00 2001 From: Kyrylo Petrov Date: Wed, 28 May 2025 20:04:31 +0300 Subject: [PATCH 1/2] chore(157): project-addons-ui --- src/app/app.routes.ts | 5 + src/app/core/constants/nav-items.constant.ts | 4 + .../project/addons/addons.component.html | 82 +++++++++++ .../project/addons/addons.component.scss | 11 ++ .../project/addons/addons.component.spec.ts | 22 +++ .../project/addons/addons.component.ts | 134 ++++++++++++++++++ .../settings/addons/addons.component.spec.ts | 2 +- .../settings/addons/addons.component.ts | 2 +- .../settings/addons/components/index.ts | 2 - .../addon-card-list.component.html | 0 .../addon-card-list.component.scss | 0 .../addon-card-list.component.spec.ts | 2 +- .../addon-card-list.component.ts | 4 +- .../addon-card/addon-card.component.html | 0 .../addon-card/addon-card.component.scss | 2 +- .../addon-card/addon-card.component.spec.ts | 4 +- .../addon-card/addon-card.component.ts | 6 +- src/app/shared/components/addons/index.ts | 2 + src/assets/i18n/en.json | 3 +- 19 files changed, 273 insertions(+), 14 deletions(-) create mode 100644 src/app/features/project/addons/addons.component.html create mode 100644 src/app/features/project/addons/addons.component.scss create mode 100644 src/app/features/project/addons/addons.component.spec.ts create mode 100644 src/app/features/project/addons/addons.component.ts delete mode 100644 src/app/features/settings/addons/components/index.ts rename src/app/{features/settings/addons/components => shared/components/addons}/addon-card-list/addon-card-list.component.html (100%) rename src/app/{features/settings/addons/components => shared/components/addons}/addon-card-list/addon-card-list.component.scss (100%) rename src/app/{features/settings/addons/components => shared/components/addons}/addon-card-list/addon-card-list.component.spec.ts (88%) rename src/app/{features/settings/addons/components => shared/components/addons}/addon-card-list/addon-card-list.component.ts (74%) rename src/app/{features/settings/addons/components => shared/components/addons}/addon-card/addon-card.component.html (100%) rename src/app/{features/settings/addons/components => shared/components/addons}/addon-card/addon-card.component.scss (90%) rename src/app/{features/settings/addons/components => shared/components/addons}/addon-card/addon-card.component.spec.ts (89%) rename src/app/{features/settings/addons/components => shared/components/addons}/addon-card/addon-card.component.ts (90%) create mode 100644 src/app/shared/components/addons/index.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index adbad36dd..5771b7b7a 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -153,6 +153,11 @@ export const routes: Routes = [ loadComponent: () => import('./features/project/settings/settings.component').then((mod) => mod.SettingsComponent), }, + { + path: 'addons', + loadComponent: () => + import('./features/project/addons/addons.component').then((mod) => mod.AddonsComponent), + }, ], }, { diff --git a/src/app/core/constants/nav-items.constant.ts b/src/app/core/constants/nav-items.constant.ts index c3c68d64f..3ad5ea87b 100644 --- a/src/app/core/constants/nav-items.constant.ts +++ b/src/app/core/constants/nav-items.constant.ts @@ -108,6 +108,10 @@ export const PROJECT_MENU_ITEMS: MenuItem[] = [ label: 'navigation.project.settings', routerLink: 'settings', }, + { + label: 'navigation.project.addons', + routerLink: 'addons', + }, ], }, ]; diff --git a/src/app/features/project/addons/addons.component.html b/src/app/features/project/addons/addons.component.html new file mode 100644 index 000000000..8311510c6 --- /dev/null +++ b/src/app/features/project/addons/addons.component.html @@ -0,0 +1,82 @@ + +
+ + @if (!isMobile()) { + + + {{ 'settings.addons.tabs.allAddons' | translate }} + + + {{ 'settings.addons.tabs.connectedAddons' | translate }} + + + } + + + @if (isMobile()) { + + + {{ selectedOption.label | translate }} + + + {{ item.label | translate }} + + + } + +

+ {{ 'settings.addons.description' | translate }} +

+
+
+ + + {{ selectedOption.label | translate }} + + + {{ item.label | translate }} + + +
+
+ +
+
+ + +
+ + + + + +
+
+
diff --git a/src/app/features/project/addons/addons.component.scss b/src/app/features/project/addons/addons.component.scss new file mode 100644 index 000000000..ef94601f7 --- /dev/null +++ b/src/app/features/project/addons/addons.component.scss @@ -0,0 +1,11 @@ +@use "assets/styles/mixins" as mix; +@use "assets/styles/variables" as var; + +:host { + @include mix.flex-column; + flex: 1; + + .tabs-container { + @include mix.flex-column; + } +} diff --git a/src/app/features/project/addons/addons.component.spec.ts b/src/app/features/project/addons/addons.component.spec.ts new file mode 100644 index 000000000..4d1a6de4c --- /dev/null +++ b/src/app/features/project/addons/addons.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AddonsComponent } from './addons.component'; + +describe('AddonsComponent', () => { + let component: AddonsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AddonsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AddonsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/project/addons/addons.component.ts b/src/app/features/project/addons/addons.component.ts new file mode 100644 index 000000000..d4205f3fd --- /dev/null +++ b/src/app/features/project/addons/addons.component.ts @@ -0,0 +1,134 @@ +import { Store } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; + +import { Select } from 'primeng/select'; +import { Tab, TabList, TabPanel, TabPanels, Tabs } from 'primeng/tabs'; + +import { ChangeDetectionStrategy, Component, computed, effect, inject, signal } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { FormsModule } from '@angular/forms'; + +import { UserSelectors } from '@core/store/user'; +import { + AddonsSelectors, + GetAddonsUserReference, + GetAuthorizedCitationAddons, + GetAuthorizedStorageAddons, + GetCitationAddons, + GetStorageAddons, +} from '@osf/features/settings/addons/store'; +import { SearchInputComponent, SubHeaderComponent } from '@shared/components'; +import { AddonCardListComponent } from '@shared/components/addons'; +import { SelectOption } from '@shared/models'; +import { IS_XSMALL } from '@shared/utils'; + +@Component({ + selector: 'osf-addons', + imports: [ + AddonCardListComponent, + SearchInputComponent, + Select, + SubHeaderComponent, + Tab, + TabList, + TabPanel, + TabPanels, + Tabs, + TranslatePipe, + FormsModule, + ], + templateUrl: './addons.component.html', + styleUrl: './addons.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AddonsComponent { + #store = inject(Store); + protected readonly defaultTabValue = 0; + protected readonly isMobile = toSignal(inject(IS_XSMALL)); + protected readonly searchValue = signal(''); + protected readonly selectedCategory = signal('external-storage-services'); + protected readonly selectedTab = signal(this.defaultTabValue); + protected readonly currentUser = this.#store.selectSignal(UserSelectors.getCurrentUser); + protected readonly addonsUserReference = this.#store.selectSignal(AddonsSelectors.getAddonUserReference); + protected readonly storageAddons = this.#store.selectSignal(AddonsSelectors.getStorageAddons); + protected readonly citationAddons = this.#store.selectSignal(AddonsSelectors.getCitationAddons); + protected readonly authorizedStorageAddons = this.#store.selectSignal(AddonsSelectors.getAuthorizedStorageAddons); + protected readonly authorizedCitationAddons = this.#store.selectSignal(AddonsSelectors.getAuthorizedCitationAddons); + protected readonly allAuthorizedAddons = computed(() => { + const authorizedAddons = [...this.authorizedStorageAddons(), ...this.authorizedCitationAddons()]; + + const searchValue = this.searchValue().toLowerCase(); + return authorizedAddons.filter((card) => card.displayName.includes(searchValue)); + }); + + protected readonly userReferenceId = computed(() => { + return this.addonsUserReference()[0]?.id; + }); + + protected readonly currentAction = computed(() => + this.selectedCategory() === 'external-storage-services' ? GetStorageAddons : GetCitationAddons + ); + + protected readonly currentAddonsState = computed(() => + this.selectedCategory() === 'external-storage-services' ? this.storageAddons() : this.citationAddons() + ); + + protected readonly filteredAddonCards = computed(() => { + const searchValue = this.searchValue().toLowerCase(); + return this.currentAddonsState().filter((card) => card.externalServiceName.includes(searchValue)); + }); + + protected readonly tabOptions: SelectOption[] = [ + { + label: 'settings.addons.tabs.allAddons', + value: 0, + }, + { + label: 'settings.addons.tabs.connectedAddons', + value: 1, + }, + ]; + + protected readonly categoryOptions: SelectOption[] = [ + { + label: 'settings.addons.categories.additionalService', + value: 'external-storage-services', + }, + { + label: 'settings.addons.categories.citationManager', + value: 'external-citation-services', + }, + ]; + + protected onCategoryChange(value: string): void { + this.selectedCategory.set(value); + } + + constructor() { + effect(() => { + // Only proceed if we have the current user + if (this.currentUser()) { + this.#store.dispatch(GetAddonsUserReference); + } + }); + + effect(() => { + // Only proceed if we have both current user and user reference + if (this.currentUser() && this.userReferenceId()) { + this.#loadAddonsIfNeeded(this.userReferenceId()); + } + }); + } + + #loadAddonsIfNeeded(userReferenceId: string): void { + const action = this.currentAction(); + const addons = this.currentAddonsState(); + + if (!addons?.length) { + this.#store.dispatch(action); + this.#store.dispatch(new GetAuthorizedStorageAddons(userReferenceId)); + this.#store.dispatch(new GetAuthorizedCitationAddons(userReferenceId)); + } + } +} diff --git a/src/app/features/settings/addons/addons.component.spec.ts b/src/app/features/settings/addons/addons.component.spec.ts index b137bf14c..9ba6009f4 100644 --- a/src/app/features/settings/addons/addons.component.spec.ts +++ b/src/app/features/settings/addons/addons.component.spec.ts @@ -9,9 +9,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { UserSelectors } from '@osf/core/store/user'; import { SearchInputComponent, SubHeaderComponent } from '@osf/shared/components'; import { IS_XSMALL } from '@osf/shared/utils'; +import { AddonCardListComponent } from '@shared/components/addons'; import { AddonsComponent } from './addons.component'; -import { AddonCardListComponent } from './components'; import { AddonsSelectors } from './store'; describe('AddonsComponent', () => { diff --git a/src/app/features/settings/addons/addons.component.ts b/src/app/features/settings/addons/addons.component.ts index c52bd8cde..a81adf4df 100644 --- a/src/app/features/settings/addons/addons.component.ts +++ b/src/app/features/settings/addons/addons.component.ts @@ -14,8 +14,8 @@ import { UserSelectors } from '@osf/core/store/user'; import { SearchInputComponent, SubHeaderComponent } from '@osf/shared/components'; import { SelectOption } from '@osf/shared/models'; import { IS_XSMALL } from '@osf/shared/utils'; +import { AddonCardListComponent } from '@shared/components/addons'; -import { AddonCardListComponent } from './components'; import { AddonsSelectors, GetAddonsUserReference, diff --git a/src/app/features/settings/addons/components/index.ts b/src/app/features/settings/addons/components/index.ts deleted file mode 100644 index 58c98ce7b..000000000 --- a/src/app/features/settings/addons/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { AddonCardComponent } from './addon-card/addon-card.component'; -export { AddonCardListComponent } from './addon-card-list/addon-card-list.component'; diff --git a/src/app/features/settings/addons/components/addon-card-list/addon-card-list.component.html b/src/app/shared/components/addons/addon-card-list/addon-card-list.component.html similarity index 100% rename from src/app/features/settings/addons/components/addon-card-list/addon-card-list.component.html rename to src/app/shared/components/addons/addon-card-list/addon-card-list.component.html diff --git a/src/app/features/settings/addons/components/addon-card-list/addon-card-list.component.scss b/src/app/shared/components/addons/addon-card-list/addon-card-list.component.scss similarity index 100% rename from src/app/features/settings/addons/components/addon-card-list/addon-card-list.component.scss rename to src/app/shared/components/addons/addon-card-list/addon-card-list.component.scss diff --git a/src/app/features/settings/addons/components/addon-card-list/addon-card-list.component.spec.ts b/src/app/shared/components/addons/addon-card-list/addon-card-list.component.spec.ts similarity index 88% rename from src/app/features/settings/addons/components/addon-card-list/addon-card-list.component.spec.ts rename to src/app/shared/components/addons/addon-card-list/addon-card-list.component.spec.ts index b6941e236..5bf262df1 100644 --- a/src/app/features/settings/addons/components/addon-card-list/addon-card-list.component.spec.ts +++ b/src/app/shared/components/addons/addon-card-list/addon-card-list.component.spec.ts @@ -2,7 +2,7 @@ import { MockComponent } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AddonCardComponent } from '../addon-card/addon-card.component'; +import { AddonCardComponent } from '@shared/components/addons/addon-card/addon-card.component'; import { AddonCardListComponent } from './addon-card-list.component'; diff --git a/src/app/features/settings/addons/components/addon-card-list/addon-card-list.component.ts b/src/app/shared/components/addons/addon-card-list/addon-card-list.component.ts similarity index 74% rename from src/app/features/settings/addons/components/addon-card-list/addon-card-list.component.ts rename to src/app/shared/components/addons/addon-card-list/addon-card-list.component.ts index e2da2a260..a502c0ebb 100644 --- a/src/app/features/settings/addons/components/addon-card-list/addon-card-list.component.ts +++ b/src/app/shared/components/addons/addon-card-list/addon-card-list.component.ts @@ -1,7 +1,7 @@ import { Component, input } from '@angular/core'; -import { Addon, AuthorizedAddon } from '../../models'; -import { AddonCardComponent } from '../addon-card/addon-card.component'; +import { Addon, AuthorizedAddon } from '@osf/features/settings/addons/models'; +import { AddonCardComponent } from '@shared/components/addons'; @Component({ selector: 'osf-addon-card-list', diff --git a/src/app/features/settings/addons/components/addon-card/addon-card.component.html b/src/app/shared/components/addons/addon-card/addon-card.component.html similarity index 100% rename from src/app/features/settings/addons/components/addon-card/addon-card.component.html rename to src/app/shared/components/addons/addon-card/addon-card.component.html diff --git a/src/app/features/settings/addons/components/addon-card/addon-card.component.scss b/src/app/shared/components/addons/addon-card/addon-card.component.scss similarity index 90% rename from src/app/features/settings/addons/components/addon-card/addon-card.component.scss rename to src/app/shared/components/addons/addon-card/addon-card.component.scss index 288440f8d..d2c4ec4e3 100644 --- a/src/app/features/settings/addons/components/addon-card/addon-card.component.scss +++ b/src/app/shared/components/addons/addon-card/addon-card.component.scss @@ -1,4 +1,4 @@ -@use "assets/styles/variables" as var; +@use "../../../../../assets/styles/variables" as var; :host { display: block; diff --git a/src/app/features/settings/addons/components/addon-card/addon-card.component.spec.ts b/src/app/shared/components/addons/addon-card/addon-card.component.spec.ts similarity index 89% rename from src/app/features/settings/addons/components/addon-card/addon-card.component.spec.ts rename to src/app/shared/components/addons/addon-card/addon-card.component.spec.ts index 84446d41c..5b04c559d 100644 --- a/src/app/features/settings/addons/components/addon-card/addon-card.component.spec.ts +++ b/src/app/shared/components/addons/addon-card/addon-card.component.spec.ts @@ -5,8 +5,8 @@ import { MockPipe, MockProvider } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { CredentialsFormat } from '../../enums'; -import { Addon } from '../../models'; +import { CredentialsFormat } from '../../../../features/settings/addons/enums'; +import { Addon } from '../../../../features/settings/addons/models'; import { AddonCardComponent } from './addon-card.component'; diff --git a/src/app/features/settings/addons/components/addon-card/addon-card.component.ts b/src/app/shared/components/addons/addon-card/addon-card.component.ts similarity index 90% rename from src/app/features/settings/addons/components/addon-card/addon-card.component.ts rename to src/app/shared/components/addons/addon-card/addon-card.component.ts index 93308abe9..2fedde7cf 100644 --- a/src/app/features/settings/addons/components/addon-card/addon-card.component.ts +++ b/src/app/shared/components/addons/addon-card/addon-card.component.ts @@ -10,10 +10,10 @@ import { Component, computed, inject, input, signal } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { Router } from '@angular/router'; -import { IS_XSMALL } from '@osf/shared/utils'; +import { IS_XSMALL } from '@shared/utils'; -import { Addon, AuthorizedAddon } from '../../models'; -import { DeleteAuthorizedAddon } from '../../store'; +import { Addon, AuthorizedAddon } from '../../../../features/settings/addons/models'; +import { DeleteAuthorizedAddon } from '../../../../features/settings/addons/store'; @Component({ selector: 'osf-addon-card', diff --git a/src/app/shared/components/addons/index.ts b/src/app/shared/components/addons/index.ts new file mode 100644 index 000000000..dee6639df --- /dev/null +++ b/src/app/shared/components/addons/index.ts @@ -0,0 +1,2 @@ +export { AddonCardComponent } from '@shared/components/addons/addon-card/addon-card.component'; +export { AddonCardListComponent } from '@shared/components/addons/addon-card-list/addon-card-list.component'; diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 5991a5a29..7d1ee9529 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -26,7 +26,8 @@ "registrations": "Registrations", "settings": "Settings", "contributors": "Contributors", - "analytics": "Analytics" + "analytics": "Analytics", + "addons": "Add-ons" } }, "auth": { From 9797cb907fffde7f0bfdba6a38ad8066c9695a0f Mon Sep 17 00:00:00 2001 From: Kyrylo Petrov Date: Mon, 2 Jun 2025 18:36:00 +0300 Subject: [PATCH 2/2] fix(project-addons): minor fixes --- .../project/addons/addons.component.html | 54 +++++++++---------- .../project/addons/addons.component.scss | 4 -- .../project/addons/addons.component.ts | 49 ++++++----------- .../project/addons/addons.constants.ts | 33 ++++++++++++ .../addon-card/addon-card.component.scss | 2 +- .../addon-card/addon-card.component.spec.ts | 4 +- .../addons/addon-card/addon-card.component.ts | 5 +- 7 files changed, 80 insertions(+), 71 deletions(-) create mode 100644 src/app/features/project/addons/addons.constants.ts diff --git a/src/app/features/project/addons/addons.component.html b/src/app/features/project/addons/addons.component.html index 8311510c6..1beaf7a06 100644 --- a/src/app/features/project/addons/addons.component.html +++ b/src/app/features/project/addons/addons.component.html @@ -1,35 +1,31 @@
- - @if (!isMobile()) { - - - {{ 'settings.addons.tabs.allAddons' | translate }} - - - {{ 'settings.addons.tabs.connectedAddons' | translate }} - - - } +