Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0cc7430
chore(157): project-addons-ui
kpetrov24exoft May 28, 2025
9797cb9
fix(project-addons): minor fixes
kpetrov24exoft Jun 2, 2025
ddd68fc
Merge branch 'main' into feat/184-project-addons-api
rnastyuk Jun 3, 2025
1e279f1
feat(project-addons-api): moved reusable addon files to the shared fo…
rnastyuk Jun 3, 2025
4d92a72
Merge branch 'main' into feat/184-project-addons-api
rnastyuk Jun 3, 2025
9cb147b
feat(profile-addons-api): added initial profile addons page structure…
rnastyuk Jun 9, 2025
988e267
Merge branch 'refs/heads/main' into feat/184-project-addons-api
rnastyuk Jun 9, 2025
2ccd258
feat(profile-addons-api): added addon configuration functionality
rnastyuk Jun 16, 2025
905ab39
Merge branch 'main' into feat/184-project-addons-api
rnastyuk Jun 16, 2025
3484429
feat(profile-addons-api): fixed connection creation bug, fixed form, …
rnastyuk Jun 18, 2025
614d12d
Merge branch 'main' into feat/184-project-addons-api
rnastyuk Jun 18, 2025
4afb7a6
feat(profile-addons-api): added breadcrumbs trimming
rnastyuk Jun 18, 2025
b040475
Merge branch 'main' into feat/184-project-addons-api
rnastyuk Jun 18, 2025
8c21899
feat(profile-addons-api): fixed comments
rnastyuk Jun 18, 2025
cd6365a
feat(profile-addons-api): changed p-select usage to reusable osf-select
rnastyuk Jun 19, 2025
3377ef7
Merge branch 'main' into feat/178-collections-api
rnastyuk Jun 20, 2025
52ef634
feat(collections-api): added get collection provider logic
rnastyuk Jun 20, 2025
b7e8669
feat(profile-addons-api): added filter logic and collections submissi…
rnastyuk Jun 25, 2025
8382bdf
feat(profile-addons-api): added dynamic rendering of the collections …
rnastyuk Jun 26, 2025
0a98a99
Merge branch 'main' into feat/178-collections-api
rnastyuk Jun 26, 2025
621bdb6
feat(add-to-collection): refactored collections routing and added bas…
rnastyuk Jun 27, 2025
b7e2dd4
feat(collections-api): refactored branding service
rnastyuk Jun 27, 2025
a7cff22
Merge branch 'feat/178-collections-api' into feat/201-add-to-collection
rnastyuk Jun 27, 2025
77bfdfb
Merge branch 'refs/heads/main' into feat/201-add-to-collection
rnastyuk Jun 30, 2025
c938d35
feat(add-to-collection): changed accordion to basic stepper
rnastyuk Jun 30, 2025
9d0d7a9
feat(add-to-collection): added stepper enum
rnastyuk Jun 30, 2025
bf3d937
Merge branch 'main' into feat/201-add-to-collection
rnastyuk Jul 2, 2025
a03e40a
feat(add-to-collection-api): changed accordion to stepper
rnastyuk Jul 3, 2025
213f8bb
Merge branch 'refs/heads/main' into feat/201-add-to-collection
rnastyuk Jul 3, 2025
dc85bf4
Merge branch 'refs/heads/main' into feat/201-add-to-collection
rnastyuk Jul 4, 2025
c13aff8
Merge branch 'refs/heads/main' into feat/201-add-to-collection
rnastyuk Jul 4, 2025
a628942
Merge branch 'refs/heads/main' into feat/201-add-to-collection
rnastyuk Jul 5, 2025
342ee27
Merge branch 'refs/heads/main' into feat/201-add-to-collection
rnastyuk Jul 8, 2025
e3a97f7
Merge branch 'refs/heads/main' into feat/201-add-to-collection
rnastyuk Jul 9, 2025
fb0f8da
feat(add-to-collection-api): added logic for submitting project to th…
rnastyuk Jul 11, 2025
6ddacb2
Merge branch 'main' into feat/201-add-to-collection
rnastyuk Jul 11, 2025
404d9bb
feat(add-to-collection-api): fixed bug with the contributor deletion
rnastyuk Jul 11, 2025
da69ecb
Merge branch 'main' into feat/201-add-to-collection
rnastyuk Jul 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { provideStates } from '@ngxs/store';

import { Routes } from '@angular/router';

import { CollectionsState } from '@osf/features/collections/store/collections';
import { ProjectsState } from '@shared/stores';

import { MyProfileResourceFiltersOptionsState } from './features/my-profile/components/filters/store';
import { MyProfileResourceFiltersState } from './features/my-profile/components/my-profile-resource-filters/store';
import { MyProfileState } from './features/my-profile/store';
Expand Down Expand Up @@ -71,10 +74,12 @@ export const routes: Routes = [
path: 'my-projects',
loadComponent: () =>
import('./features/my-projects/my-projects.component').then((mod) => mod.MyProjectsComponent),
providers: [provideStates([CollectionsState])],
},
{
path: 'my-projects/:id',
loadChildren: () => import('./features/project/project.routes').then((mod) => mod.projectRoutes),
providers: [provideStates([ProjectsState])],
},
{
path: 'settings',
Expand Down
2 changes: 0 additions & 2 deletions src/app/core/constants/ngxs-states.constant.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { AuthState } from '@core/store/auth';
import { UserState } from '@core/store/user';
import { CollectionsState } from '@osf/features/collections/store';
import { MeetingsState } from '@osf/features/meetings/store';
import { MyProjectsState } from '@osf/features/my-projects/store';
import { ProjectMetadataState } from '@osf/features/project/metadata/store';
Expand All @@ -26,7 +25,6 @@ export const STATES = [
AccountSettingsState,
NotificationSubscriptionState,
ProjectOverviewState,
CollectionsState,
WikiState,
MeetingsState,
RegistrationsState,
Expand Down
34 changes: 1 addition & 33 deletions src/app/features/collections/collections.component.html
Original file line number Diff line number Diff line change
@@ -1,33 +1 @@
@if (!isProviderLoading()) {
<section class="collections flex flex-column flex-1 pt-0 xl:pt-5">
<div
class="collections-sub-header flex justify-content-between flex-column gap-4 mb-4 sm:mb-6 sm:gap-0 sm:flex-row"
>
<div class="flex gap-3">
<i class="collections-icon text-white osf-icon-collections"></i>
<h1 class="flex align-items-center text-white">{{ collectionProvider()?.name }}</h1>
</div>

<p-button class="collections-heading-btn" [label]="'collections.buttons.addToCollection' | translate" />
</div>

<div class="search-input-container">
<osf-search-input
[control]="searchControl"
[showHelpIcon]="true"
[placeholder]="'collections.searchInput.placeholder' | translate"
(helpClicked)="openHelpDialog()"
(triggerSearch)="onSearchTriggered($event)"
/>
@if (collectionProvider()?.description) {
<div class="mt-6 text-white" [innerHTML]="collectionProvider()?.description"></div>
}
</div>

<div class="content-container flex-1">
<osf-collections-main-content />
</div>
</section>
} @else {
<osf-loading-spinner />
}
<router-outlet />
38 changes: 0 additions & 38 deletions src/app/features/collections/collections.component.scss
Original file line number Diff line number Diff line change
@@ -1,38 +0,0 @@
@use "assets/styles/variables" as var;
@use "assets/styles/mixins" as mix;

:host {
--collection-bg-color: #013b5c;
@include mix.flex-column;
flex: 1;
}

.collections {
background: var(--collection-bg-color);
border-top: none;

.collections-sub-header {
margin: mix.rem(48px) mix.rem(28px);

.collections-icon {
font-size: mix.rem(42px);
}
}

.search-input-container {
margin: 0 mix.rem(28px) mix.rem(48px) mix.rem(28px);
position: relative;

img {
position: absolute;
right: mix.rem(4px);
top: mix.rem(4px);
z-index: 1;
}
}

.content-container {
background: var.$white;
padding: mix.rem(28px);
}
}
171 changes: 4 additions & 167 deletions src/app/features/collections/collections.component.ts
Original file line number Diff line number Diff line change
@@ -1,174 +1,11 @@
import { createDispatchMap, select } from '@ngxs/store';

import { TranslatePipe, TranslateService } from '@ngx-translate/core';

import { Button } from 'primeng/button';
import { DialogService } from 'primeng/dynamicdialog';

import { debounceTime } from 'rxjs';

import { ChangeDetectionStrategy, Component, computed, DestroyRef, effect, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';

import { CollectionsHelpDialogComponent, CollectionsMainContentComponent } from '@osf/features/collections/components';
import { CollectionsFilters } from '@osf/features/collections/models';
import { CollectionsQuerySyncService } from '@osf/features/collections/services';
import {
ClearCollections,
ClearCollectionSubmissions,
CollectionsSelectors,
GetCollectionDetails,
GetCollectionProvider,
GetCollectionSubmissions,
SetPageNumber,
SetSearchValue,
} from '@osf/features/collections/store';
import { LoadingSpinnerComponent, SearchInputComponent } from '@shared/components';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
selector: 'osf-collections',
imports: [SearchInputComponent, TranslatePipe, Button, CollectionsMainContentComponent, LoadingSpinnerComponent],
imports: [RouterOutlet],
templateUrl: './collections.component.html',
styleUrl: './collections.component.scss',
providers: [DialogService, CollectionsQuerySyncService],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CollectionsComponent {
private router = inject(Router);
private route = inject(ActivatedRoute);
private dialogService = inject(DialogService);
private translateService = inject(TranslateService);
private querySyncService = inject(CollectionsQuerySyncService);
private destroyRef = inject(DestroyRef);

protected searchControl = new FormControl('');
protected providerId = signal<string>('');

protected collectionProvider = select(CollectionsSelectors.getCollectionProvider);
protected collectionDetails = select(CollectionsSelectors.getCollectionDetails);
protected selectedFilters = select(CollectionsSelectors.getAllSelectedFilters);
protected sortBy = select(CollectionsSelectors.getSortBy);
protected searchText = select(CollectionsSelectors.getSearchText);
protected pageNumber = select(CollectionsSelectors.getPageNumber);
protected isProviderLoading = select(CollectionsSelectors.getCollectionProviderLoading);
protected primaryCollectionId = computed(() => this.collectionProvider()?.primaryCollection?.id);

protected actions = createDispatchMap({
getCollectionProvider: GetCollectionProvider,
getCollectionDetails: GetCollectionDetails,
setSearchValue: SetSearchValue,
getCollectionSubmissions: GetCollectionSubmissions,
setPageNumber: SetPageNumber,
clearCollections: ClearCollections,
clearCollectionsSubmissions: ClearCollectionSubmissions,
});

constructor() {
this.initializeProvider();
this.setupEffects();
this.setupSearchBinding();
}

protected openHelpDialog(): void {
this.dialogService.open(CollectionsHelpDialogComponent, {
focusOnShow: false,
header: this.translateService.instant('collections.helpDialog.header'),
closeOnEscape: true,
modal: true,
closable: true,
});
}

protected onSearchTriggered(searchValue: string): void {
this.actions.setSearchValue(searchValue);
this.actions.setPageNumber('1');
}

private initializeProvider(): void {
const id = this.route.snapshot.paramMap.get('id');
if (!id) {
this.router.navigate(['/not-found']);
return;
}

this.providerId.set(id);
this.actions.getCollectionProvider(id);
}

private setupEffects(): void {
this.querySyncService.initializeFromUrl();

effect(() => {
const collectionId = this.primaryCollectionId();
if (collectionId) {
this.actions.getCollectionDetails(collectionId);
}
});

effect(() => {
const searchText = this.searchText();
const sortBy = this.sortBy();
const selectedFilters = this.selectedFilters();
const pageNumber = this.pageNumber();

if (searchText !== undefined && sortBy !== undefined && selectedFilters && pageNumber) {
this.querySyncService.syncStoreToUrl(searchText, sortBy, selectedFilters, pageNumber);
}
});

effect(() => {
const searchText = this.searchText();
const sortBy = this.sortBy();
const selectedFilters = this.selectedFilters();
const pageNumber = this.pageNumber();
const providerId = this.providerId();
const collectionDetails = this.collectionDetails();

if (searchText !== undefined && selectedFilters && pageNumber && providerId && collectionDetails) {
const activeFilters = this.getActiveFilters(selectedFilters);
this.actions.clearCollectionsSubmissions();
this.actions.getCollectionSubmissions(providerId, searchText, activeFilters, pageNumber, sortBy);
}
});

effect(() => {
this.destroyRef.onDestroy(() => {
this.actions.clearCollections();
});
});
}

private getActiveFilters(filters: CollectionsFilters): Record<string, string[]> {
return Object.entries(filters)
.filter(([key, value]) => value.length)
.reduce(
(acc, [key, value]) => {
acc[key] = value;
return acc;
},
{} as Record<string, string[]>
);
}

private setupSearchBinding(): void {
effect(() => {
const storeSearchText = this.searchText();
const currentControlValue = this.searchControl.value;

if (storeSearchText !== currentControlValue) {
this.searchControl.setValue(storeSearchText, { emitEvent: false });
}
});

this.searchControl.valueChanges
.pipe(debounceTime(300), takeUntilDestroyed(this.destroyRef))
.subscribe((searchValue) => {
const trimmedValue = searchValue?.trim() || '';
if (trimmedValue !== this.searchText()) {
this.actions.setSearchValue(trimmedValue);
}
});
}
}
export class CollectionsComponent {}
24 changes: 22 additions & 2 deletions src/app/features/collections/collections.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { provideStates } from '@ngxs/store';

import { Routes } from '@angular/router';

import { AddToCollectionState } from '@osf/features/collections/store/add-to-collection';
import { CollectionsState } from '@osf/features/collections/store/collections';
import { ContributorsState, ProjectsState } from '@shared/stores';

import { ModeratorsState } from '../moderation/store/moderation';

export const collectionsRoutes: Routes = [
Expand All @@ -12,14 +16,30 @@ export const collectionsRoutes: Routes = [
path: '',
pathMatch: 'full',
loadComponent: () =>
import('@core/components/page-not-found/page-not-found.component').then((mod) => mod.PageNotFoundComponent),
import('@core/components/page-not-found/page-not-found.component').then((m) => m.PageNotFoundComponent),
data: { skipBreadcrumbs: true },
},
{
path: ':id',
redirectTo: ':id/discover',
},
{
path: ':id/discover',
pathMatch: 'full',
loadComponent: () =>
import('@osf/features/collections/collections.component').then((mod) => mod.CollectionsComponent),
import('@osf/features/collections/components/collections-discover/collections-discover.component').then(
(mod) => mod.CollectionsDiscoverComponent
),
providers: [provideStates([CollectionsState])],
},
{
path: ':id/add',
pathMatch: 'full',
loadComponent: () =>
import('@osf/features/collections/components/add-to-collection/add-to-collection.component').then(
(mod) => mod.AddToCollectionComponent
),
providers: [provideStates([ProjectsState, CollectionsState, AddToCollectionState, ContributorsState])],
},
{
path: ':id/moderation',
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div class="dialog-container">
<p>
{{ 'collections.addToCollection.confirmationDialogMessage' | translate }}
</p>

<div class="flex pt-5 gap-3 justify-content-between">
<p-button
[label]="'common.buttons.cancel' | translate"
severity="info"
(click)="dialogRef.close(false)"
[disabled]="isSubmitting()"
class="btn-full-width"
/>
<p-button
[label]="'common.buttons.addToCollection' | translate"
(click)="handleAddToCollectionConfirm()"
[loading]="isSubmitting()"
class="btn-full-width"
/>
</div>
</div>
Loading