Skip to content

Commit ce4151b

Browse files
chore(157): project-addons-ui (#83)
* chore(157): project-addons-ui * fix(project-addons): minor fixes
1 parent 986535e commit ce4151b

20 files changed

+282
-14
lines changed

src/app/app.routes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ export const routes: Routes = [
162162
loadComponent: () =>
163163
import('./features/project/settings/settings.component').then((mod) => mod.SettingsComponent),
164164
},
165+
{
166+
path: 'addons',
167+
loadComponent: () =>
168+
import('./features/project/addons/addons.component').then((mod) => mod.AddonsComponent),
169+
},
165170
],
166171
},
167172
{

src/app/core/constants/nav-items.constant.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ export const PROJECT_MENU_ITEMS: MenuItem[] = [
120120
label: 'navigation.project.settings',
121121
routerLink: 'settings',
122122
},
123+
{
124+
label: 'navigation.project.addons',
125+
routerLink: 'addons',
126+
},
123127
],
124128
},
125129
];
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<osf-sub-header [showButton]="false" [title]="'settings.addons.header.title' | translate" [icon]="'settings'" />
2+
<section class="flex-column flex flex-1">
3+
<p-tabs [value]="selectedTab()" class="hidden md:flex md:flex-1">
4+
<p-tablist class="pr-5 pl-5">
5+
<p-tab [value]="AddonTabValue.ALL_ADDONS">
6+
{{ 'settings.addons.tabs.allAddons' | translate }}
7+
</p-tab>
8+
<p-tab [value]="AddonTabValue.CONNECTED_ADDONS">
9+
{{ 'settings.addons.tabs.connectedAddons' | translate }}
10+
</p-tab>
11+
</p-tablist>
12+
13+
<p-tabpanels class="p-3 sm:p-5 flex-1">
14+
<p-select
15+
class="w-full mb-4 md:hidden"
16+
[options]="tabOptions"
17+
optionValue="value"
18+
[ngModel]="selectedTab()"
19+
(ngModelChange)="selectedTab.set($event)"
20+
>
21+
<ng-template #selectedItem let-selectedOption>
22+
{{ selectedOption.label | translate }}
23+
</ng-template>
24+
<ng-template #item let-item>
25+
{{ item.label | translate }}
26+
</ng-template>
27+
</p-select>
28+
<p-tabpanel [value]="AddonTabValue.ALL_ADDONS" class="flex flex-column gap-5">
29+
<p>
30+
{{ 'settings.addons.description' | translate }}
31+
</p>
32+
<section class="grid">
33+
<div class="col-12 md:col-6 lg:col-3">
34+
<p-select
35+
[placeholder]="'settings.addons.filters.addonType' | translate"
36+
[options]="categoryOptions"
37+
optionValue="value"
38+
[ngModel]="selectedCategory()"
39+
(ngModelChange)="onCategoryChange($event)"
40+
class="w-full"
41+
>
42+
<ng-template #selectedItem let-selectedOption>
43+
{{ selectedOption.label | translate }}
44+
</ng-template>
45+
<ng-template #item let-item>
46+
{{ item.label | translate }}
47+
</ng-template>
48+
</p-select>
49+
</div>
50+
<div class="col-12 md:col-6 lg:col-9">
51+
<osf-search-input
52+
class="w-full"
53+
[searchValue]="searchValue()"
54+
(searchValueChange)="searchValue.set($event || '')"
55+
[placeholder]="'settings.addons.filters.search' | translate"
56+
/>
57+
</div>
58+
</section>
59+
60+
<osf-addon-card-list [cards]="filteredAddonCards()" [showDangerButton]="false" />
61+
</p-tabpanel>
62+
<p-tabpanel [value]="AddonTabValue.CONNECTED_ADDONS" class="flex flex-column gap-5">
63+
<osf-search-input
64+
class="flex-1"
65+
[searchValue]="searchValue()"
66+
(searchValueChange)="searchValue.set($event || '')"
67+
[placeholder]="'settings.addons.filters.search' | translate"
68+
/>
69+
70+
<osf-addon-card-list
71+
[cards]="allAuthorizedAddons()"
72+
[cardButtonLabel]="'settings.addons.form.buttons.reconnect' | translate"
73+
[showDangerButton]="true"
74+
/>
75+
</p-tabpanel>
76+
</p-tabpanels>
77+
</p-tabs>
78+
</section>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@use "assets/styles/mixins" as mix;
2+
@use "assets/styles/variables" as var;
3+
4+
:host {
5+
@include mix.flex-column;
6+
flex: 1;
7+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { AddonsComponent } from './addons.component';
4+
5+
describe('AddonsComponent', () => {
6+
let component: AddonsComponent;
7+
let fixture: ComponentFixture<AddonsComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [AddonsComponent],
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(AddonsComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
});
18+
19+
it('should create', () => {
20+
expect(component).toBeTruthy();
21+
});
22+
});
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { select, Store } from '@ngxs/store';
2+
3+
import { TranslatePipe } from '@ngx-translate/core';
4+
5+
import { Select } from 'primeng/select';
6+
import { Tab, TabList, TabPanel, TabPanels, Tabs } from 'primeng/tabs';
7+
8+
import { ChangeDetectionStrategy, Component, computed, effect, inject, signal } from '@angular/core';
9+
import { toSignal } from '@angular/core/rxjs-interop';
10+
import { FormsModule } from '@angular/forms';
11+
12+
import { UserSelectors } from '@core/store/user';
13+
import {
14+
AddonsSelectors,
15+
GetAddonsUserReference,
16+
GetAuthorizedCitationAddons,
17+
GetAuthorizedStorageAddons,
18+
GetCitationAddons,
19+
GetStorageAddons,
20+
} from '@osf/features/settings/addons/store';
21+
import { SearchInputComponent, SubHeaderComponent } from '@shared/components';
22+
import { AddonCardListComponent } from '@shared/components/addons';
23+
import { IS_XSMALL } from '@shared/utils';
24+
25+
import { ADDON_CATEGORY_OPTIONS, ADDON_TAB_OPTIONS, AddonCategoryValue, AddonTabValue } from './addons.constants';
26+
27+
@Component({
28+
selector: 'osf-addons',
29+
imports: [
30+
AddonCardListComponent,
31+
SearchInputComponent,
32+
Select,
33+
SubHeaderComponent,
34+
Tab,
35+
TabList,
36+
TabPanel,
37+
TabPanels,
38+
Tabs,
39+
TranslatePipe,
40+
FormsModule,
41+
],
42+
templateUrl: './addons.component.html',
43+
styleUrl: './addons.component.scss',
44+
changeDetection: ChangeDetectionStrategy.OnPush,
45+
})
46+
export class AddonsComponent {
47+
protected readonly AddonTabValue = AddonTabValue;
48+
#store = inject(Store);
49+
protected readonly defaultTabValue = AddonTabValue.ALL_ADDONS;
50+
protected readonly isMobile = toSignal(inject(IS_XSMALL));
51+
protected readonly searchValue = signal('');
52+
protected readonly selectedCategory = signal<string>(AddonCategoryValue.EXTERNAL_STORAGE_SERVICES);
53+
protected readonly selectedTab = signal<number>(this.defaultTabValue);
54+
protected readonly currentUser = select(UserSelectors.getCurrentUser);
55+
protected readonly addonsUserReference = select(AddonsSelectors.getAddonUserReference);
56+
protected readonly storageAddons = select(AddonsSelectors.getStorageAddons);
57+
protected readonly citationAddons = select(AddonsSelectors.getCitationAddons);
58+
protected readonly authorizedStorageAddons = select(AddonsSelectors.getAuthorizedStorageAddons);
59+
protected readonly authorizedCitationAddons = this.#store.selectSignal(AddonsSelectors.getAuthorizedCitationAddons);
60+
protected readonly allAuthorizedAddons = computed(() => {
61+
const authorizedAddons = [...this.authorizedStorageAddons(), ...this.authorizedCitationAddons()];
62+
63+
const searchValue = this.searchValue().toLowerCase();
64+
return authorizedAddons.filter((card) => card.displayName.includes(searchValue));
65+
});
66+
67+
protected readonly userReferenceId = computed(() => {
68+
return this.addonsUserReference()[0]?.id;
69+
});
70+
71+
protected readonly currentAction = computed(() =>
72+
this.selectedCategory() === AddonCategoryValue.EXTERNAL_STORAGE_SERVICES ? GetStorageAddons : GetCitationAddons
73+
);
74+
75+
protected readonly currentAddonsState = computed(() =>
76+
this.selectedCategory() === AddonCategoryValue.EXTERNAL_STORAGE_SERVICES
77+
? this.storageAddons()
78+
: this.citationAddons()
79+
);
80+
81+
protected readonly filteredAddonCards = computed(() => {
82+
const searchValue = this.searchValue().toLowerCase();
83+
return this.currentAddonsState().filter((card) => card.externalServiceName.includes(searchValue));
84+
});
85+
86+
protected readonly tabOptions = ADDON_TAB_OPTIONS;
87+
protected readonly categoryOptions = ADDON_CATEGORY_OPTIONS;
88+
89+
protected onCategoryChange(value: string): void {
90+
this.selectedCategory.set(value);
91+
}
92+
93+
constructor() {
94+
effect(() => {
95+
// Only proceed if we have the current user
96+
if (this.currentUser()) {
97+
this.#store.dispatch(GetAddonsUserReference);
98+
}
99+
});
100+
101+
effect(() => {
102+
// Only proceed if we have both current user and user reference
103+
if (this.currentUser() && this.userReferenceId()) {
104+
this.#loadAddonsIfNeeded(this.userReferenceId());
105+
}
106+
});
107+
}
108+
109+
#loadAddonsIfNeeded(userReferenceId: string): void {
110+
const action = this.currentAction();
111+
const addons = this.currentAddonsState();
112+
113+
if (!addons?.length) {
114+
this.#store.dispatch(action);
115+
this.#store.dispatch(new GetAuthorizedStorageAddons(userReferenceId));
116+
this.#store.dispatch(new GetAuthorizedCitationAddons(userReferenceId));
117+
}
118+
}
119+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { SelectOption } from '@shared/models';
2+
3+
export enum AddonTabValue {
4+
ALL_ADDONS = 0,
5+
CONNECTED_ADDONS = 1,
6+
}
7+
8+
export enum AddonCategoryValue {
9+
EXTERNAL_STORAGE_SERVICES = 'external-storage-services',
10+
EXTERNAL_CITATION_SERVICES = 'external-citation-services',
11+
}
12+
13+
export const ADDON_TAB_OPTIONS: SelectOption[] = [
14+
{
15+
label: 'settings.addons.tabs.allAddons',
16+
value: AddonTabValue.ALL_ADDONS,
17+
},
18+
{
19+
label: 'settings.addons.tabs.connectedAddons',
20+
value: AddonTabValue.CONNECTED_ADDONS,
21+
},
22+
];
23+
24+
export const ADDON_CATEGORY_OPTIONS: SelectOption[] = [
25+
{
26+
label: 'settings.addons.categories.additionalService',
27+
value: AddonCategoryValue.EXTERNAL_STORAGE_SERVICES,
28+
},
29+
{
30+
label: 'settings.addons.categories.citationManager',
31+
value: AddonCategoryValue.EXTERNAL_CITATION_SERVICES,
32+
},
33+
];

src/app/features/settings/addons/addons.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
99
import { UserSelectors } from '@osf/core/store/user';
1010
import { SearchInputComponent, SubHeaderComponent } from '@osf/shared/components';
1111
import { IS_XSMALL } from '@osf/shared/utils';
12+
import { AddonCardListComponent } from '@shared/components/addons';
1213

1314
import { AddonsComponent } from './addons.component';
14-
import { AddonCardListComponent } from './components';
1515
import { AddonsSelectors } from './store';
1616

1717
describe('AddonsComponent', () => {

src/app/features/settings/addons/addons.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import { UserSelectors } from '@osf/core/store/user';
1414
import { SearchInputComponent, SubHeaderComponent } from '@osf/shared/components';
1515
import { SelectOption } from '@osf/shared/models';
1616
import { IS_XSMALL } from '@osf/shared/utils';
17+
import { AddonCardListComponent } from '@shared/components/addons';
1718

18-
import { AddonCardListComponent } from './components';
1919
import {
2020
AddonsSelectors,
2121
GetAddonsUserReference,

src/app/features/settings/addons/components/index.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)